Predict what Python will print in response to each of these expressions. Then try it and make sure your answer was correct, or if not, that you understand why!

# Q1 a = 1 b = a + 1 a + b + a * b ______________ # Q2 a == b ______________ # Q3 z, y = 1, 2 print(z) ______________ # Q4 def square(x): print(x * x) # Hit enter twice a = square(b) ______________ # Q5 print(a) ______________ # Q6 def square(y): return y * y # Hit enter twice a = square(b) print(a) _______________

1. What would Python print? Try to figure it out before you type it into the interpreter!

# Q1 a, b = 10, 6 a > b and a == 0 _______________ # Q2 a > b or a == 0 _______________ # Q3 not a > 0 _______________ # Q4 a != 0 _______________ # Q5 True and False _______________ # Q6 True and True _______________ # Q7 True or False _______________ # Q8 False or False _______________ # Q9 True and True or True and False _______________

**Boolean order of operations:** just like with mathematical operators,
boolean operators (and, or,
and not) have an order of operations, too:

# highest priority not and or # lowest priority

For example, the following expression will evaluate to True:

True and not False or not True and False

It might be easier to rewrite the expression like this:

(True and (not False)) or ((not True) and False)

If you find writing parentheses to be clearer, it is perfectly acceptable to do so in your code.

**Short-circuit operators:** in Python, and and
or are examples of *short-circuit operators*.
Consider the following line of code:

10 > 3 or 1 / 0 != 1

Generally, operands are evaluated from left to right in Python. The expression 10 > 3 will be evaluated first, then 1 / 0 != 1 will be evaluated. The problem is, evaluating 1 / 0 will cause Python to raise an error! (You can try dividing by 0 in the interpreter)

However, the original line of code will not cause any errors -- in fact, it will evaluate to True. This is made possible due to short-circuiting, which works in the following ways:

- and will evaluate to True only if
*all*the operands are True. For multiple and statements, Python will go left to right until it runs into the first False value -- then it will just immediately evaluate to False. - or will evaluate to True if
*at least one*of the operands is True. For multiple or statements, Python will go left to right until it runs into the first True value -- then it will immediately evaluate to True.

2. What happens when you type in the following expressions into the interpreter?

>>> True and False and 1 / 0 == 1 >>> True and 1 / 0 == 1 and False >>> False or 1 / 0 == 1 or True >>> True or 1 / 0 == 1 >>> 1 and 2 and 3 >>> 1 and 2 and 0 and 5 >>> 0 or 3 or False >>> 1 and 2 or 0 and 1 / 0 >>> 2 and (4 - 2 * 2) or 0 and 1 / 0

Short-circuiting allows you to write boolean expressions while avoiding errors. Using division by zero as an example:

x != 0 and 3 / x > 3

In the line above, the first operand is used to guard against a ZeroDivisionError that could be caused by the second operand.

3. What would Python print?

a, b = 10, 6 # Q1 if a==b: a else: b _______________ # Q2 if a == 4: 6 elif b >= 4: 6 + 7 + a else: 25 ________________ # Q3 # ';' lets you type multiple commands on one line if b != a: a; b _________________

The following are some **common mistakes** when using if statements:

1. Using '=' instead of '==':
remember, = (single equals) is used for *assignment*,
while == (double equals) is used for *comparison*.

# bad if a = b: print("uh oh!") # good! if a == b: print("yay!")

2. Multiple comparisons: for example, trying to check if both x and y are greater than 0.

# bad if x and y > 0: print("uh oh!") # good! if x > 0 and y > 0: print("yay!")

3. Redundancy: this is more of a style issue, rather than a practical error

# bad result = (x % 2 == 0) if result == True: # redundant print("even") # good result = (x % 2 == 0) if result: print("even")

In the above snippet of code, result is True, so the if statement would really be saying

if True == True:

which is redundant.

Consider the following function:

def abs(x): if x >= 0: return x else: return -x

It is syntactically correct to rewrite abs in the following way:

def abs(x): if x >= 0: return x return -x # missing else statement!

This is possible as a direct consequence of how return
works -- when Python sees a return statement, it will
*immediately terminate* the function. In the above example, if x >= 0,
Python will never reach the final line. Try to convince yourself that this is indeed the
case before moving on.

Keep in mind that **guarded commands only work if the function is terminated**!
For example, the following function will *always* print "less than zero", because
the function is not terminated in the body of the if suite:

def foo(x): if x > 0: print("greater than zero") print("less than zero") >>> foo(-3) less than zero >>> foo(4) greater than zero less than zero

In general, using guarded commands will make your code more concise -- however, if you find that it makes your code harder to read, by all means use an else statement.

4. What would Python print?

n = 2 def exp_decay(n): if n % 2 != 0: return while n > 0: print(n) n = n // 2 # See exercise 3 for an explanation of what '//' stands for exp_decay(1024) __________________ exp_decay(5) __________________

5. Before we write our next function, let's look at the idea of floor division (rounds down to the nearest integer) versus true division (decimal division).

True Division | Floor Division |
---|---|

>>> 1 / 4 | >>> 1 // 4 |

0.25 | 0 |

>>> 4 / 2 | >>> 4 // 2 |

2.0 | 2 |

>>> 5 / 3 | >>> 5 // 3 |

1.666666666667 | 1 |

Thus, if we have an operator "%" that gives us the remainder of dividing two numbers, we can see that the following rule applies:

b * (a // b) + (a % b) = a

Now, define a procedure factors(n) which takes in a number, n, and prints out all of the numbers that divide n evenly. For example, a call with n=20 should result as follows (order doesn’t matter):

>>> factors(20) 20 10 5 4 2 1

Helpful Tip: You can use the % to find if something divides evenly into a number. % gives you a remainder, as follows:

>>> 10 % 5 0 >>> 10 % 4 2 >>> 10 % 7 3 >>> 10 % 2 0

A *predicate* is a function that checks if a given input
x satisfies a certain condition. For example, the
following function is_even(x) checks if a given number
x is even.

def is_even(x): if x % 2 == 0: return True else: return False

The if statement checks if x is divisible by 2 -- if it is, the predicate returns True.

The current is_even function is syntactically valid, but it exhibits poor style (i.e. it's ugly). The following is a more concise way of writing is_even(x):

def is_even(x): return x % 2 == 0

Try to understand why these two versions do exactly the same thing. The expression x % 2 == 0 evaluates to either True or False. Think of it this way:

def is_even(x): result = (x % 2 == 0) if result == True: # this is also bad style, but gets the point across return result else: # if result is False return result

No matter what happens, we are always going to return result!

def is_even(x): result = (x % 2 == 0) # parenthesis used for clarification, not necessary return result

Since result is equivalent to x % 2 == 0, we can just return that expression

def is_even(x): return x % 2 == 0

6. Define a predicate function is_ascending, which takes an argument n and checks if digits are in ascending order, from right to left. Read the doctests for examples:

def is_ascending(n): """Returns True if the digits of n are in ascending order from right to left. >>> is_ascending(321) True >>> is_ascending(5553220) True >>> is_ascending(4352) False >>> is_ascending(3) True >>> is_ascending(12345) False """ "*** YOUR CODE HERE ***"

By now, you've probably seen a couple of error messages. Even though they might look intimidating, error messages are actually very helpful in debugging code. The following are some common error messages (found at the bottom of a traceback):

**SyntaxError**: Indicates that your code contains improper syntax (e.g. missing a colon after an if statement).**IndentationError**: Indicates that your code contains improper indentation (e.g. inconsistent indentation of a function body)**TypeError**: Indicates an attempted operation on incompatible types (e.g. trying to add a boolean and an int)**ZeroDivisionError**: Indicates an attempted division by zero.

Using these descriptions of error messages, you should be able to get a beter idea of what
went wrong with your code. **If you run into error messages, try to identify the problem
before asking for help.**