Advanced Python
In-Class Exercise Solutions, Session 5
FUNCTIONS WITH KEYWORD ARGUMENTS |
|
Ex. 5.1 | Write a function multiply() that takes one positional argument (an expected integer value) and one keyword argument (an optional multiplier called mul=). Make the default for mul= the value 2. Call as shown. |
def multiply(val, mul=2): # val: int, 5; mul: int, 3 (first call below)
mval = val * mul # int, 15
return mval # return 15
x = multiply(5, mul=3) # int, 15
print(x)
y = multiply(5) # int, 10
print(y)
|
|
Ex. 5.2 | Write a function greet() with one positional argument (a first name) and one keyword argument (an optional last name). Make the default for last= the value None. Call as shown. |
def greet(first, last=None): # first: str, 'Joe'; last: None (first call below)
if not last: # bool, True
return f'Hi, {first}!' #
else:
return f'Hi, {first} {last}!' #
x = greet('Joe') # str, 'Hi, Joe!'
print(x)
y = greet('Joe', last='Wilson') # str, 'Hi, Joe Wilson!'
print(y)
|
|
OBJECT REFERENCES |
|
Ex. 5.3 | One list or two? How can we test? |
x = ['a', 'b', 'c'] # list, ['a', 'b', 'c']
y = x # (reference to list above)
x.append('d') # list, ['a', 'b', 'c', 'd']
print(y)
# changing x and seeing the change in y demonstrates that
# there is only one list. y has been pointed to the same
# list that x is pointing to.
|
|
Ex. 5.4 | Two lists or three? Is the list in the last item of y a duplicate list? How can we test? |
x = ['a', 'b', 'c'] # list, ['a', 'b', 'c']
y = [1, 2, 3, x] # list, [1, 2, 3, (reference to list above)]
x.append('d') # list, ['a', 'b', 'c', 'd']
print(y[-1]) # list, ['a', 'b', 'c', 'd']
# again, changing x and seeing the change in the last element
# of y demonstrates that there is only one list. y has
# been pointed to the same list that x is pointing to.
|
|
REFERENCES PASSED TO FUNCTIONS |
|
Ex. 5.5 | One list or two? Is the list in appendme() a new list? How can we test? |
def appendme(arg): # arg: list, ['a', 'b', 'c']
arg.append('d') # list, ['a', 'b', 'c', 'd']
x = ['a', 'b', 'c'] # list, ['a' 'b', 'c']
appendme(x)
print(x)
|
|
Ex. 5.6 | greet() has been assigned to y. Call the function through variable y. |
def greet():
print('hello, world')
y = greet # (reference to function above)
y() # (calls greet())
|
|
Ex. 5.7 | Call callit(), passing greet as argument, then inside callit(), call the function through the argument (do not call greet()). |
def callit(func): # func: function object
func() # (calls greet())
def greet():
print('hello, world!')
callit(greet)
|
|
SORTING |
|
Ex. 5.8 | Given the below code, sort the list in reverse numeric order and print it. |
Suggested Solution:
numlist = [1, 13, 23, 3, 9, 16] # list, [1, 13, 23, 3, 9, 16]
sortlist = sorted(numlist, reverse=True) # list, [23, 16, 13, 9, 3, 1]
print(sortlist)
|
|
The reverse=True parameter argument to sorted() simply reverses the sort order, last to first. True can be placed literally or it can even be a variable of the value True. |
|
Ex. 5.9 | Given the below code, sort the list in alphabetic order and print it. |
Suggested Solution:
charlist = ['a', 'z', 'b', 'c', 'd', 'f'] # list, ['a', 'z', 'b', 'c', 'd', 'f']
sortlist = sorted(charlist) # list, ['a', 'b', 'c', 'd', 'f, 'z']
print(sortlist)
|
|
This exercise is simply demonstrating that when asked to sort strings, sorted() sorts them in an an alphabetic order (as long as all strings are the same case). |
|
Ex. 5.10 | Given the below code, sort the list using standard sort and print it (note the output below). |
Suggested Solution:
charlist = ['F', 'e', 'c', 'a', 'B', 'D'] # list, ['F', 'e', 'c', 'a', 'B', 'D']
sortlist = sorted(charlist) # list, ['B', 'D', 'F', 'a', 'c', 'e']
print(sortlist)
|
|
Demonstrates that when strings are a mix of uppercase and lowercase, sorted() defaults to "asciibetic", which refers to each character's position on the ascii character table (google "ascii table" and choose "image" to see examples). As you can see, uppercase letters come before lowercase letters in this table. |
|
COMPLEX SORTING |
|
Ex. 5.11 | Given the below code, sort the list in alphabetical order and print it. |
Suggested Solution:
charlist = ['F', 'e', 'c', 'a', 'B', 'D'] # list, ['F', 'e', 'c', 'a', 'B', 'D']
sortlist = sorted(charlist, key=str.lower) # list, ['a', 'B', 'c', 'D', 'e', 'F']
print(sortlist)
|
|
The str.lower method, when passed to sorted(), causes sorted() to lowercase each string before sorting (more accurately, to sort each string by its lowercase value), so that case no longer matters when sorting strings. |
|
Ex. 5.12 | Given your understanding that the key= argument to sorted() will in a sense process each element through whatever function we pass to it, sort these strings by their length, and print the sorted list. |
Suggested Solution:
mystrs = ['I', 'was', 'hanging', 'on', 'a', 'rock'] # list, ['I', 'was', 'hanging' on' ... ]
sorted_list = sorted(mystrs, key=len) # list, ['I', 'a', 'on', 'was', 'rock', 'hanging']
print(sorted_list)
|
|
As you know, len() returns the integer length (# of characters) of a string. Therefore by passing the len function to sorted(), we are asking sorted() to pass each string through len() and sort that string by the returned value -- thus all strings are sorted by their respective lengths. |
|
Ex. 5.13 | Given the following numbers, which were retrieved from a file as strings, sort these strings numerically without creating a new list. |
Suggested Solution:
mynums = ['5', '101', '10', '1', '3'] # list, ['5', '101', '10', '1', '3']
sorted_list = sorted(mynums, key=int) # list, ['1', '3', '5', '10', '101']
print(sorted_list)
|
|
To solve this, you would want to ask what function would take an element and return its integer value. Of course int() does this, so by passing int to sorted() we are asking sorted() to pass each string to int() and then sort that string by the return value of int(), or its integer value. |
|
Ex. 5.14 | Experiential exercise. See the discussion in the exercise description for details. |
Ex. 5.15 | Given the below list, sort line_list by the number at the end of each line. Loop through and print the sorted list. |
Suggested Solution:
line_list = [
'the value 3',
'this value 1',
'that value is 4',
'the value here is 2'
] # list of strings
def by_end_num(line): # line: str, 'the value 3' (first item)
words = line.split() # list, ['the', 'value', '3']
return int(words[-1]) # return 3
for line in sorted(line_list, key=by_end_num): # str, 'this value 1'
print(line)
|
|
Again you should begin by identifying first what is being sorted (in this case, each string line), and the value by which one of these elements should be sorted (in this case, the integer value of the number at the end of the string). So your function's job is to expect a string line as argument, to split off the number at the end, to convert that number from a string to an int, and then to return that int. Don't forget that a sort function's job is to process all elements, but it does so only one element at a time -- sorted() will feed each element to the function and keep track of the return value so that it can sort an element by your function's return value. |
|
Ex. 5.16 | Sort the lines of the file pyku.txt by the number of words on each line. |
Suggested Solution:
pyku = '../pyku.txt' # str, '../pyku.txt'
def by_num_words(line): # line: str, "We're out of gouda.\n"
words = line.split() # list, ["We're", 'out', 'of', 'gouda.\n']
return len(words) # return 4
fh = open(pyku) # 'file' object
lines = fh.readlines() # list, ["We're out of gouda.\n", 'This parrot...' ]
for line in sorted(lines, key=by_num_words): # str, "We're out of gouda.\n"
line = line.rstrip() # str, "We're out of gouda."
print(line)
|
|
Again, the steps are: 1) identify the elements that will be sorted a list of strings -- in this case the lines from the file; 2) decide what value the line should be sorted -- in this case the number of words in the line, which can be obtained with len(line.split()); 3) inside the function, take the single line as argument, split the line and take the integer len() of the resulting list, and return that integer. |
|
Ex. 5.17 | Sort the lines of revenue.csv by the numeric value in the last field by passing the readlines() of the file to sorted() and using a custom sort sub similar to an earlier exercise. |
Suggested Solution:
revenue = '../revenue.csv' # str, '../revenue.csv'
def by_last_float(line): # line: str, "Haddad's,PA,239.5\n"
words = line.split(',') # list, ["Haddad's", 'PA', '239.5\n']
return float(words[-1]) # return 239.5
fh = open(revenue) # 'file' object
lines = fh.readlines() # list, ["Haddad's,PA,239.5\n", 'Westfield'...]
for line in sorted(lines, key=by_last_float): # list 'Dothraki Fashions,NY,5.98\n'
print(line)
|
|
This brings us closest to what we will do in the homework. Again, if an item to be sorted is a line from the file, and the value by which it should be sorted is the integer value of the last field, then our sort function needs only to split the line into items, convert the last item to a float, and return the float. |
|
SORTING WITH LAMBDA FUNCTIONS |
|
Ex. 5.18 | Convert a function to lambda. |
The following function takes one argument and returns the value doubled. Convert to lambda and use in the map() function. |
|
Suggested Solution:
def doubleit(arg): # arg: int, 1
return arg * 2 # return 2
seq = [1, 2, 3, 4] # list, [1, 2, 3, 4]
seq2 = map(lambda arg: arg * 2, seq) # map object: [2, 4, 6, 8]
print(list(seq2)) # [2, 4, 6, 8]
|
|
Ex. 5.19 | Convert another function to lambda. |
The below function by_last_float() takes a string argument and returns a portion of that string (converted to float) as return value. Replace this function with a lambda. |
|
Suggested Solution:
revenue = '../revenue.csv' # str, '../revenue.csv'
def by_last_float(line): # str, "Haddad's,PA,239.5\n"
words = line.split(',') # list, ["Haddad's", 'PA', '239.5\n']
return float(words[-1]) # return 239.5
fh = open(revenue) # 'file' object
lines = fh.readlines() # list, ["Haddad's,PA,239.5\n", 'Westfield'...]
for line in sorted(lines, key=lambda line: float(line.split(',')[-1])): # str, 'Dothraki Fashions,NY,5.98\n'
line = line.rstrip() # str, 'Dothraki Fashions,NY,5.98'
print line
|
|
Ex. 5.20 | Use a lambda to sort strings as floats. |
Given the below list of strings that have float values, write a lambda to sort these values by their float equivalents. (Although this is possible without using a custom function or lambda, we would like to practice lambdas here.) |
|
fvals = ['39.5', '3.0', '300.3', '2.002', '20.95',
'200.1', '21.3', '20.03'] # list, ['39.5', '3.0' ... ]
svals = sorted(fvals, lambda val: float(val)) # list, ['2.002', '20.03',
# '29.95', '21.3', '200.1',
# '3.0', '39.5', '300.3']
print(svals)
|
|
Ex. 5.21 | Use a lambda to sort a dict by value. |
As you know, sorting a dict means sorting its keys. Write a lambda that expects a dict key as argument, and returns the value for that key as return value. |
|
d = {'a': 10, 'b': 2, 'c': 5} # dict, {'a': 10, 'b': 2, 'c': 5}
skeys = sorted(d, key=lambda dict_key: d[dict_key]) # list, ['b', 'c', 'a']
print(skeys)
|
|
SORTING COMPLEX STRUCTURES |
|
Ex. 5.22 | Given the following list of dicts, sort the dicts by each dict's last name. |
Loop through and print each dict. |
|
Suggested Solution:
people = [
{ 'id': '12345',
'fname': 'Joe',
'lname': 'Wilson',
'addr': '12345 Main Street' },
{ 'id': '00355',
'fname': 'Marie',
'lname': 'Adams',
'addr': '888 Elm Street' },
{ 'id': '98973',
'fname': 'Alex',
'lname': 'Wilson',
'addr': '23 Marsh Avenue' }
] # list of dicts
def list_by_lname(this_dict): # this_dict: {'id: '12345', 'fname' ... }
return this_dict['lname'] # return 'Wilson'
sdicts = sorted(people, key=list_by_dict_addr) # list of dicts, sorted by lname
for this_dict in sdicts: # {'id': '00355', 'fname': 'Marie' ... }
print(this_dict)
|
|
Ex. 5.23 | Given the following dict of dicts, sort the dict keys by the last name. Loop through and print each key. |
Suggested Solution:
people = { '12345': { 'fname': 'Joe',
'lname': 'Wilson',
'addr': '12345 Main Street' },
'00355': { 'fname': 'Marie',
'lname': 'Adams',
'addr': '888 Elm Street' },
'98973': { 'fname': 'Alex',
'lname': 'Wilson',
'addr': '23 Marsh Avenue' }
} # dict of dicts
def dict_by_dict_addr(dict_key): # str, '12345'
return people[dict_key]['lname'] # return 'Wilson'
skeys = sorted(people, key=dict_by_dict_addr) # list, ['00355', '12345', '98973']
|
|
HIGHER ORDER FUNCTIONS |
|
Ex. 5.24 | map() and filter() functions: call below functions to see result. Play with return value of functions to see altered result. |
def doubleit(arg): # arg: int, -3
return arg * 2 # return -6
def is_positive(arg): # arg: int, -3
if arg > 0: # bool, False
return True
else:
return False # return False
proclist = [-3, -2, -1, 0, 1, 2, 3] # list, [-3, -2, -1 ... ]
transformed_list = map(doubleit, proclist) # list, [-6, -4, -1, 0, 2, ... ]
filtered_list = filter(is_positive, proclist) # list, [1, 2, 3]
print('* mapped list *')
print(list(transformed_list))
print()
print('* filtered list *')
print(list(filtered_list))
|
|
Ex. 5.25 | greet has been assigned to callit. Call greet inside the function. |
def callit(arg): # arg: function 'greet'
arg() # calling greet()
def greet():
print('hello, world')
callit(greet)
|
|
Ex. 5.26 | greet has been returned from returnit. Call greet outside the function. |
def returnit():
def greet():
print('hello, world')
return greet # return 'greet' function
x = returnit() # function, 'greet'
x() # (prints 'hello, world')
|
|
Ex. 5.27 | Demo: a "decorator" function -- a function that both takes a function as argument and returns a function as return value. Use the prior concepts to understand what is happening here. |
def my_decorator(func): # func: function, 'whoop'
def wrapper():
print("Something is happening before whoop() is called.")
func() # calls 'whoop'
print("Something is happening after whoop() is called.")
return wrapper # return function 'wrapper'
def whoop():
print("Wheee!")
# now the same name points to a replacement function
whoop = my_decorator(whoop) # function 'wrapper'
# calling the replacement function
whoop() # (calls function wrapper)
|
|
Ex. 5.28 | Same function as last time -- this time use @ ("pie syntax") to produce a "true" Python decorator. Place the decorator just above the function def for this_function, and remove the call to my_decorator(). |
def my_decorator(func): # func: function, 'whoop'
def wrapper():
print("Something is happening before whoop() is called.")
func() # calls whoop()
print("Something is happening after whoop() is called.")
return wrapper # return function 'wrapper'
@my_decorator
def this_function():
print("Wheee!")
# calling the replacement function
this_function() # (calls function wrapper)
|
|
Ex. 5.29 | Understanding *args and **kwargs: the below function has 2 positional arguments and 2 keyword arguments, and prints the arguments inside the function. Call the function as shown, and see the the arguments printed. Now replace the arguments in the signature with *args and **kwargs and print the variables args and kwargs inside the function. |
def printargs(*args, **kwargs): # args: tuple, (5, 10); kwargs: dict, {'y': 100, 'z': 'Hello!'}
print(args, kwargs)
printargs(5, 10, y=100, z='Hello!')
|
|
Ex. 5.30 | Same decorator function as last time -- this_function() has been defined to take two positional arguments and two keyword arguments, printing these arguments inside this_function()). Inside wrapper(), we accept these arguments and pass them to some_function() (which is this_function(). Run this program to see that it works. Now replace the positional arguments with *args and the keyword arguments with **kwargs and see that it works in the same way. |
def my_decorator(func): # func: function, 'whoop'
def wrapper(*args, **kwargs): # args: tuple, (5, 10); kwargs: dict, {'x': 99, 'y': 'hey!'}
print("Something is happening before whoop() is called.")
func(*args, **kwargs) # calling whoop(5, 10, x=99, y='hey!')
print("Something is happening after whoop() is called.")
return wrapper # return function 'wrapper'
@my_decorator # replacing whoop with wrapper
def whoop(a, b, x=None, y=None):
print(f"Wheee!: {a}, {b}, {x}, {y}")
# calling the replacement function
whoop(5, 10, x=99, y='hey!') # calling wrapper(5, 10, x=99, y='hey!')
|
|