Sometimes, you can append certain "flags" on the command line to
inspect your code further. Here are a few useful ones that'll come in
handy this semester. If you want to learn more about other python
flags, you can type man python
no flags: Adding no flags will directly run your Python script, meaning that Python will run the code in the file you provide and return you to the command line.
python3 FILE_NAME
-i: The -i option runs your Python script, and throws you into an interactive session. If you omit the -i option, Python will only run your script. See the next section regarding interactive sessions to learn more!
python3 -i FILE_NAME
-m doctest: Using -m doctest option will be useful on your homeworks and projects to help you test your code by showing you whether your code is working as you intend it to. Doctests are marked by triple quotations (""") and are usually located within the function.
python3 -m doctest FILE_NAME
-v: The -v option signifies a verbose option. You can append this flag to the -m doctest flag to show both passing and failing tests. With the -v flag, you will be notified of all results (both failing and passing tests).
python3 -m doctest -v FILE_NAME
Sometimes, you just want to try some things out in the python
interpreter. If you want to test out functions in a file, you'll need
the -i
flag as we specified above.
However, if you just need to try something out in the interpreter, without any user defined functions this is how you start an interactive session:
python3
On Cygwin:
python3 -i
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! Remember, to start Python, type python3
into the command line.
>>> a = 4
>>> b = a
>>> a = 10
>>> a == b
______________
>>> z, y = 1, 2
>>> print(z)
______________
>>> def square(x):
... print(x * x) # Hit enter twice
...
>>> a = square(2)
______________
>>> print(a)
______________
>>> def square(y):
... return y * y # Hit enter twice
...
>>> a = square(2)
>>> print(a)
_______________
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 or False
_______________
# Q7
>>> not True and False
_______________
# Q8
>>> not (True and False)
_______________
# Q9
>>> False or False
_______________
# Q10
>>> True and True or True and False
_______________
Just like mathematical operators, boolean operators (and
, or
,
and not
) have an order of operations:
not
(highest priority)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.
In Python, and
and or
are examples of short-circuiting operators.
Consider the following 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, stopping evaluation altogether! (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
.Some examples:
>>> True and False and 1 / 0 == 1 # stops at the False
False
>>> True and 1 / 0 == 1 and False # hits the division by zero
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
>>> True or 1 / 0 == 1 # stops at the True
True
>>> False or 1 / 0 == 1 or True # hits the division by zero
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
What would the Python interpreter display?
>>> a, b = 10, 6
>>> if a == b:
... a
... else:
... b
...
_______________
>>> if a == 4:
... 6
... elif b >= 4:
... 6 + 7 + a
... else:
... 25
...
________________
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!")
...
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!")
else
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 a direct consequence of how return
works -- when
Python sees a return
statement, it will immediately terminate the
function, and the rest of the function will not be evaluated. 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 omitting the else
only works 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, omitting the else
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.
What would Python print?
>>> n = 2
>>> def exp_decay(n):
... if n % 2 != 0:
... return
... while n > 0:
... print(n)
... n = n // 2
...
>>> exp_decay(1024)
__________________
>>> exp_decay(5)
__________________
>>> def funky(k):
... while k < 50:
... if k % 2 == 0:
... k += 13
... else:
... k += 1
... print(k)
... return k
>>> funky(25)
__________________
>>> n, i = 7, 0
>>> while i < n:
... i += 2
... print(i)
__________________
>>> n = 3
>>> while n > 0:
... n -= 1
... print(n)
__________________
>>> n = 3
>>> while n >= 0:
... n -= 1
... print(n)
__________________
>>> # typing Ctrl-C will stop infinite loops
>>> n = 4
>>> while True:
... n -= 1
... print(n)
__________________
Before we write our next function, let's compare the ideas of true
division (single slash /
in Python; does decimal division), floor
division (double slash //
in Python; rounds down to the nearest
integer), and modulo (percent sign %
in Python; similar to a
remainder):
True Division (decimal division):
>>> 1 / 4
0.25
>>> 4 / 2
2.0
>>> 5/3
1.666666666667
Floor Division (integer division):
>>> 1 // 4
0
>>> 4 // 2
2
>>> 5 // 3
1
Modulo (similar to a remainder):
>>> 1 % 4
1
>>> 4 % 2
0
>>> 5 % 3
2
Given the operators above, the following relationship holds:
b * (a // b) + (a % b) = a
Now, define a function 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
Hint: One common use of the %
operator is to find if something
divides evenly into a number. For example:
>>> 10 % 5
0
>>> 10 % 4
2
>>> 10 % 7
3
>>> 10 % 2
0
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):
if
statement).Using these descriptions of error messages, you should be able to get a better idea of what went wrong with your code. If you run into error messages, try to identify the problem before asking for help. You can often Google unknown error messages to see what similar mistakes others have made to help you debug your own code.
Here's a link to a helpful Debugging Guide written by Albert Wu.
Higher order functions are functions that take a function as an input, and/or output a function. We will be exploring many applications of higher order functions.
For each question, try to determine what Python would print. Then check in the interactive interpreter to see if you got the right answer.
>>> def square(x):
... return x*x
>>> def neg(f, x):
... return -f(x)
# Q1
>>> neg(square, 4)
_______________
>>> def first(x):
... x += 8
... def second(y):
... print('second')
... return x + y
... print('first')
... return second
...
# Q2
>>> f = first(15)
_______________
# Q3
>>> f(16)
_______________
>>> def foo(x):
... def bar(y):
... return x + y
... return bar
>>> boom = foo(23)
# Q4
>>> boom(42)
_______________
# Q5
>>> foo(6)(7)
_______________
>>> func = boom
# Q6
>>> func is boom
_______________
>>> func = foo(23)
# Q7
>>> func is boom
_______________
>>> def troy():
... abed = 0
... while abed < 10:
... def britta():
... return abed
... abed += 1
... abed = 20
... return britta
...
>>> annie = troy()
>>> def shirley():
... return annie
>>> pierce = shirley()
# Q8
>>> pierce()
________________
If you haven't found this gem already, tutor.composingprograms.com has a great visualization tool for environment diagrams. Post in your python code and it will generate an environment diagram you can walk through step-by-step! Use it to help you check your answers!
Try drawing environment diagrams for the following examples and predicting what Python will output:
# Q1
def square(x):
return x * x
def double(x):
return x + x
a = square(double(4))
# Q2
x, y = 4, 3
def reassign(arg1, arg2):
x = arg1
y = arg2
reassign(5, 6)
# Q3
def f(x):
f(x)
print, f = f, print
a = f(4)
b = print(4)
# Q4
def adder_maker(x):
def adder(y):
return x + y
return adder
add3 = adder_maker(3)
add3(4)
sub5 = adder_maker(-5)
sub5(6)
sub5(10) == add3(2)
Define a function cycle
which takes in three functions as arguments:
f1
, f2
, f3
. cycle
will then return another function. The
returned function should take in an integer argument n
and do the
following:
x
and does the following:
n
is 0, just return x
n
is 1, apply the first function that is passed to cycle
to x
n
is 2, the first function passed to cycle
is applied to
x
, and then the second function passed to cycle is applied to
the result of that (i.e. f2(f1(x))
)n
is 3, apply the first, then the second, then the third
function (i.e. 3(f2(f1(x)))
)n
is 4, apply the first, then the second, then the third,
then the first function (i.e. f1(f3(f2(f1(x))))
)Hint: most of the work goes inside the most nested function.
def cycle(f1, f2, f3):
""" Returns a function that is itself a higher order function
>>> def add1(x):
... return x + 1
...
>>> def times2(x):
... return x * 2
...
>>> def add3(x):
... return x + 3
...
>>> my_cycle = cycle(add1, times2, add3)
>>> identity = my_cycle(0)
>>> identity(5)
5
>>> add_one_then_double = my_cycle(2)
>>> add_one_then_double(1)
4
>>> do_all_functions = my_cycle(3)
>>> do_all_functions(2)
9
>>> do_more_than_a_cycle = my_cycle(4)
>>> do_more_than_a_cycle(2)
10
>>> do_two_cycles = my_cycle(6)
>>> do_two_cycles(1)
19
"""
"*** YOUR CODE HERE ***"