Advanced Python
Projects, Session 5

5.1 Do another git commit. Please add a new file or make a change to an existing file, and commit and push the change to github. Again, please paste your git URL here so I can view it. Thanks!

 
5.2 line_by_date(): a sorting helper function that helps sorted() to sort a line from the file by its "canonical date" (a value that would sort in YYYYMMDD order).
The file looks like this (in the file dated_file.csv):
10/15/2018,A,B,28.3
09/03/2018,A,C,23.85
09/15/2018,B,A,19.09
09/12/2018,B,C,17.3
12/13/2017,A,B,0.3
10/08/2018,A,C,9.83
10/06/2018,A,B,18.2
10/03/2018,C,B,9.3
12/06/2017,B,A,9.9
Sorted, it should look like this (written to the file sorted_file.csv):
12/06/2017,B,A,9.9
12/13/2017,A,B,0.3
09/03/2018,A,C,23.85
09/12/2018,B,C,17.3
09/15/2018,B,A,19.09
10/03/2018,C,B,9.3
10/06/2018,A,B,18.2
10/08/2018,A,C,9.83
10/15/2018,A,B,28.3


When done, your program writes to a new file called sorted_file.csv, which will appear in your project folder after writing the file. Opening the file, you should see the lines in dated order as shown above. For extra credit, convert your function to a lambda. (A bit awkward and probably not the best choice, but it can be done.) There are at least 3 "sortable values" that you might return from your sort function. One of them is a datetime object, which you can construct using the string date. This is probably the most elegant solution as a list of datetime objects will sort correctly as dates (although there is nothing wrong with any other sortable value you might create (see discussion for ideas)). For even more credit and challege, research the datetime module and see if you can convert each date to a datetime or date object to be returned for sorting. Important hint: a common misconception is that the sorting helper function (here it is called line_by_date()) takes the entire list of lines as argument, or loops through the lines. However, a sorting helper function only works with one item to be sorted, which in this case means one line. Remember that the function expects only one line as argument and returns the value by which that line should be sorted. The sorting helper function will not loop through items -- it works with only one item. (It is the sorted() function itself that does the work of looping through items and comparing them to determine the correct order. Please see the discussion for hints on how to sort these lines by date.

 
5.3 A sorting helper function that helps sorted() to sort a list of dicts by each dict's mean_temp: opening and loading the file weny_lod_tiny.json, please sort the dicts in this list by the mean_temp value. Loop through and print each dict.
start with this code:
def by_mean_temp(arg):
    # your code here


fh = open('weny_lod_tiny.json')
lod = json.load(fh)

sorted_dicts = sorted(lod, key=by_mean_temp)

for idict in sorted_dicts:
    print(idict)
Expected Output:
{'date': '1/2/16', 'mean_temp': '93', 'precip': '0', 'events': ''}
{'date': '1/1/16', 'mean_temp': '98', 'precip': '0', 'events': ''}
{'date': '1/3/16', 'mean_temp': '101', 'precip': '0', 'events': ''}

If you're seeing the temperature values out of order, consider by what type of value by which you're asking each dict to be sorted. Important hint: a common misconception is that the sorting helper function takes the entire list of lines as argument, or loops through the lines. Instead, a sorting helper function only works with one item to be sorted, which in this case means one dict in this list of dicts. Remember that the function expects only one dict as argument and must return the value by which that dict should be sorted. The sorting helper function will not loop through items -- it works with only one item.

 
5.4 A sorting helper function that helps sorted() to sort the keys in a dict of dicts by each dict's mean_temp, then prints each key and associated dict.

Hint: you may refer to the dictionary, a global variable, inside the function. Usually we discourage referring to a global inside a function but with sort functions it is much more acceptable.

start with this code:
def by_mean_temp(arg):
    # your code here


fh = open('weny_dod_tiny.json')
dod = json.load(fh)

keys = sorted(dod, key=by_mean_temp)

for key in keys:
    print(f'{key}:  {dod[key]}')
Expected Output:
1/2/16:  {'date': '1/2/16', 'mean_temp': '93', 'precip': '0', 'events': ''}
1/1/16:  {'date': '1/1/16', 'mean_temp': '98', 'precip': '0', 'events': ''}
1/3/16:  {'date': '1/3/16', 'mean_temp': '101', 'precip': '0', 'events': ''}

Please note that it's OK to refer to the dict dod inside the function. If you're seeing the temperature values out of order, consider by what type of value by which you're asking each dict to be sorted. Important hint: a common misconception is that the sorting helper function takes the entire list of lines as argument, or loops through the lines. Instead, a sorting helper function only works with one item to be sorted, which in this case means one dict in this list of dicts. Remember that the function expects only one dict as argument and must return the value by which that dict should be sorted. The sorting helper function will not loop through items -- it works with only one item.

 
5.5 (Extra credit) Decorator Function. Write a decorator function logfunc() that logs the entry and exit of every function in a program by printing the datetime as well as the function's name.
import time

@logfunc
def greet(greeting, place='world'):
    time.sleep(2)
    return f'{greeting}, {place}!'


msg = greet('hello')            # prints:  Fri Jul 10 17:07:37 2020  entering greet(('hello',), {})
                                #          Fri Jul 10 17:09:37 2020  leaving greet()

print(msg)                      # hello, world!


msg2 = greet('goodbye', place='Mars')  # prints:  Fri Jul 10 17:07:37 2020  entering greet(('goodbye',),
                                       #                                    {'place': 'Mars'})
                                       #          Fri Jul 10 17:09:37 2020  leaving greet()

print(msg2)                            # goodbye, Mars!

logfunc() is intended to record the start and end times of a function call, which might help us identify bottlenecks in our system - which functions are taking too long to execute. The function we are decorating takes 2 seconds to execute (because we used time.sleep(2)). The decorator should be able to be applied to any function with any argument signature; you would use the *args and **kwargs catchall arguments to do this -- see slides and discussion. You can get the name of a function through the function object's .__name__ attribute. You can get the formatted current time through the time.ctime() function.

Extra credit / challenge: format the tuple arguments and dict arguments in the output so that they look like arguments:
Fri Jul 10 20:27:42 2020  entering greet('hello')

Fri Jul 10 20:27:42 2020  entering greet('goodbye', place='Mars')

This should be able to work with any function decorated with any number of positional or keyword arguments. You can do this by constructing a string based on values found in the *args tuple and the **kwargs dict. Two features that may make this easy are the list comprehension with a dict, and the .join() string method. See if you can get *args and **kwargs handled each with one statement! Note that the below examples show how these features work, not how to use them for the above purpose.

mylist = ['a', 'b', 'c', 'd']

joined = '|'.join(mylist)           # str, 'a|b|c|d'


mydict = {'a': 1, 'b': 2, 'c': 3}

print(mydict.items())               # list of tuples, [('a', 1), ('b', 2), ('c', 3)]

paired = [ f'{t[0]}|{t[1]}' for t in mydict.items() ]
                                    # list, ['a|1', 'b|2', 'c|3']
 
5.6 (extra credit) Sort a dict of lists by sum of list values associated with each key. Start with the dict built in the dict of lists extra credit assignment (no need to repeat any code from that assignment -- simply paste this dict directly into your code):
year_mktrfs = {
  '1926':  [0.97, 0.44],
  '1927':  [0.83, 0.3, 0.0, 0.72],
  '1928':  [0.43, 0.34, 0.71]
}

Using a sorting helper function, sort the dict keys by the sum of values for each key. Loop through the sorted keys and print the key and the list of values for that key, in the following order (highest sum to lowest):

Expected Output:
1927 [0.83, 0.3, 0.0, 0.72]
1928 [0.43, 0.34, 0.71]
1926 [0.97, 0.44]

Finally, once everything is working, convert your function to a lambda.

 
5.7 (extra credit) Travel to and sort the keys of a dict of dicts that is embedded in a deeply nested structure: open ip_routes_tiny.json in this week's zip archive, and determine the path that can lead you to the dict associated with the key "routes".

You should assign this dict of dicts to a new variable called routes so you can conveniently work with it.

Now sort the keys in routes by the value for the key "preference". sorted() will return a list of keys; the sorted list should print thusly:
['209.191.231.0/24', '63.72.28.64/27', '162.111.204.64/27']

Note in the above list of ips that the "preference" value for each ip is in ascending order. Finally, once everything is working, convert your function to a lambda.

 
[pr]