More on functions, loops, dictionaries, and files#

The concepts introduced in previous sessions have additional functionalities worth introducing. In this session, we will therefore look at advanced (combined) uses of functions, loops, and file I/O, which allow you to code more efficiently.

Let’s start by looking at an example for a function that will accompany us throughout this session. Let’s figure out what this does:

#an example
import itertools

#line1 ="Hello world"
# ["Hello","world"]
#line2= "A b c"
#["A","b","c"]
#[["Hello","world"],["A","b","c"]]
def sort_text(file): #takes a plaintext file as input
    words = []
    lines = [line.strip().split() for line in file] 
    print("For your reference, this is what lines looks like now: ", lines[1:10],"\n\n\n")   
    #print(list(itertools.chain.from_iterable(lines)))
    for word in itertools.chain.from_iterable(lines): 
        words.append(word) 
    words.sort()
    return words
    
unsorted_file = open("hitchhikers_guide_full.txt", "r")

print(sort_text(unsorted_file)[1:500]) #prints part of the output, not the full book
For your reference, this is what lines looks like now:  [["HITCHHIKER'S", 'GUIDE'], [], ['Complete', '&', 'Unabridged'], [], [], ['Contents:'], [], [], ['Introduction:', 'A', 'Guide', 'to', 'the', 'Guide']] 



['"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"\'Between', '"\'But,\'', '"\'Cos', '"\'E', '"\'Ere,', '"\'Lord,', '"\'Oh', '"\'Oh,', '"\'Pleiades', '"\'Protect', '"\'Reverse', '"\'That\'s', '"\'They\'ve...\'', '"\'Up,', '"\'What\'s', '"\'cos', '"\'known', '"\'orrible.', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...and..."', '"...be', '"...but', '"...denied', '"...refused', '"?...', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"A', '"ANOTHER', '"Aaaaaaarggggghhhhhh!"', '"Aaaargggh..."', '"About', '"About', '"About', '"About..."', '"Absolute', '"Access', '"According', '"Actually', '"Address', '"Admittedly,"', '"Advice,', '"Advice,', '"Advice,"', '"Afraid?"', '"Africa', '"After', '"After', '"After', '"After', '"Afterlife', '"Afterlife,', '"Agenda', '"Ah', '"Ah', '"Ah', '"Ah', '"Ah', '"Ah', '"Ah', '"Ah', '"Ah', '"Ah', '"Ah', '"Ah!', '"Ah!', '"Ah!', '"Ah!"', '"Ah!",', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah,"', '"Ah.', '"Ah.', '"Ah.', '"Ah.', '"Ah.', '"Ah."', '"Ah."', '"Ah."', '"Ah."', '"Ah."', '"Ah..."', '"Aha,', '"Aha,', '"Ahm,', '"Aliens.', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"All', '"Almighty', '"Alright', '"Alright!"', '"Alright,', '"Alright,', '"Alright,', '"Alright,', '"Alright,', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright,"', '"Alright."', '"Always', '"Am', '"Am', '"Am', '"Amazing', '"Amazing', '"American', '"Among', '"An', '"An', '"An', '"An', '"An', '"An', '"An', '"An', '"An', '"An', '"An', '"An', '"AnS...?"', '"Ancient', '"Ancient', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And', '"And,', '"And,', '"And,"', '"And,"', '"And,"', '"And...?"', '"And...?"', '"Anti-depressants,"', '"Any', '"Any', '"Any', '"Any', '"Any', '"Any', '"Anyone', '"Anyone\'s', '"Anything', '"Anything', '"Anything,"', '"Anyway,', '"Anyway,"', '"Anyway,"', '"Anyway,"', '"Anyway,"']

Here is the same code again, but commented for its functionality:

#an example
import itertools

def sort_text(file): #takes a plaintext file as input
    # initialize the 'words' list
    words = list()
    #build 'lines' w/ list comprehension: 
    #  for each line of text, 
    #  strip off white spaces and 
    #  split (make a list of words)
    # resutls in a list of lists or words
    lines = [line.strip().split() for line in file]
    #iterate through /nested/ list
    for word in itertools.chain.from_iterable(lines):   
        #add all words to flattened list of words (removes more nesting!)
        words.append(word) 
    words.sort() #sort list
    return words
    
unsorted_file = open("hitchhikers_guide_full.txt", "r")

print(sort_text(unsorted_file)[1:50])#prints part of the output, not the full book
['"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"\'Between', '"\'But,\'', '"\'Cos', '"\'E', '"\'Ere,', '"\'Lord,', '"\'Oh', '"\'Oh,', '"\'Pleiades', '"\'Protect', '"\'Reverse', '"\'That\'s', '"\'They\'ve...\'', '"\'Up,', '"\'What\'s', '"\'cos', '"\'known', '"\'orrible.', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...']

Handling functions more efficiently#

Recap on functions:

  • Functions are reusable blocks of code that perform an action, like computing a value, on the basis of potentially changing parameters.

  • Functions are defined by a def statement, which specifies the name and arguments of the function.

  • The indented code inside of the function block is the body of the function.

  • Functions are executed by calling them; function calls use the function’s name followed by parentheses, possibly containing a number of arguments within the parentheses.

Function calls are expressions, therefore they evaluate to objects (see session 02). This means you can directly operate on them:

#reset file handle to the beginning
#  (more on this below)
unsorted_file.seek(0) 

#transform return value from function to a tuple
#  then store result in a variable
sorted_tuple = tuple(sort_text(unsorted_file)) 

Note also that if you use the result of a function call only once it is unnecessary to assign it to a variable:

unsorted_file.seek(0) #reset file handle. 

# long-winded, explicit code:
sorted_file = sort_text(unsorted_file)
print(sorted_file[1:50])

unsorted_file.seek(0) 
# compressed, short code:
print(sort_text(unsorted_file)[1:50])
['"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"\'Between', '"\'But,\'', '"\'Cos', '"\'E', '"\'Ere,', '"\'Lord,', '"\'Oh', '"\'Oh,', '"\'Pleiades', '"\'Protect', '"\'Reverse', '"\'That\'s', '"\'They\'ve...\'', '"\'Up,', '"\'What\'s', '"\'cos', '"\'known', '"\'orrible.', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...']
['"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"', '"\'Between', '"\'But,\'', '"\'Cos', '"\'E', '"\'Ere,', '"\'Lord,', '"\'Oh', '"\'Oh,', '"\'Pleiades', '"\'Protect', '"\'Reverse', '"\'That\'s', '"\'They\'ve...\'', '"\'Up,', '"\'What\'s', '"\'cos', '"\'known', '"\'orrible.', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...', '"...']

Some functions and methods don’t return anything (= return None), but still modify the objects passed as their arguments. The list method sort() is such an example. Trying to assign the result of the function call to a new variable (presumably, you’d want a sorted list from sort()?) will not have the effect you want:

# this will sort `x` but not return anything 
x = [3,5,1,2,4]
x_sorted = x.sort()   # sort operates on `x` w/o returning anything 
print(type(x))        # x is sorted
print(type(x_sorted)) # x_sorted is empty
<class 'list'>
<class 'NoneType'>

Don’t forget that functions can call other functions!

def count_word_occurence(file):
    sorted_file = sort_text(file) #function call to user-defined function "sort_text"
    word = input()
    #a = sorted_file.count(word)
    return "The word '{}' occurs {} times in the text.".format(word,sorted_file.count(word)) 
    #note that we can use placeholders {} in the string, filled by the two objects named later

unsorted_file.seek(0) #reset file handle.

count_word_occurence(unsorted_file)
---------------------------------------------------------------------------
StdinNotImplementedError                  Traceback (most recent call last)
Cell In[6], line 10
      6     #note that we can use placeholders {} in the string, filled by the two objects named later
      8 unsorted_file.seek(0) #reset file handle.
---> 10 count_word_occurence(unsorted_file)

Cell In[6], line 3, in count_word_occurence(file)
      1 def count_word_occurence(file):
      2     sorted_file = sort_text(file) #function call to user-defined function "sort_text"
----> 3     word = input()
      4     #a = sorted_file.count(word)
      5     return "The word '{}' occurs {} times in the text.".format(word,sorted_file.count(word))

File /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/ipykernel/kernelbase.py:1260, in Kernel.raw_input(self, prompt)
   1258 if not self._allow_stdin:
   1259     msg = "raw_input was called, but this frontend does not support input requests."
-> 1260     raise StdinNotImplementedError(msg)
   1261 return self._input_request(
   1262     str(prompt),
   1263     self._parent_ident["shell"],
   1264     self.get_parent("shell"),
   1265     password=False,
   1266 )

StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.

More on loops#

Recap on ranges#

One of the most basic iterables is the range. The range function generates iterable sequences of numbers based on upto 3 input parameters: range(i,n,k) produces integers in the range from i to n-1, but only produces every kth integer in that sequence.

Ranges are widespread and versatile for iterating over collections of elements (lists, tuples, sets, dictionaries).

for i in range(len(sorted_file)): #iterate over full length of list
    for j in range(17,20): #iterate over sequence of integers between 17 and 19
        if len(sorted_file[i])==j:
            print("The string '{}' has {} letters"
                  .format(sorted_file[i], j)) 
The string '"Anti-depressants,"' has 19 letters
The string '"By-products...,"' has 17 letters
The string '"Errrrrrrrrrrr..."' has 18 letters
The string '"Follow-through?"' has 17 letters
The string '"Hummmmmmmyummmmmmm' has 19 letters
The string '"Mmmmmwwwwwerrrrr?"' has 19 letters
The string '"Slartibartfast's' has 17 letters
The string '"Slartibartfast,"' has 17 letters
The string '"Slartibartfast?"' has 17 letters
The string '"Ugghhhuuggghhhrrrr' has 19 letters
The string '"tzjin-anthony-ks"' has 18 letters
The string '0107-095-295-9051).' has 19 letters
The string 'Breathe-o-Smart!"' has 17 letters
The string 'Fintlewoodlewix..."' has 19 letters
The string 'IMPOSSIBILITIES".' has 17 letters
The string 'Improbability-proof' has 19 letters
The string 'Infinity-minus-one' has 18 letters
The string 'Know-Nothing-Bozo' has 17 letters
The string 'Know-Nothing-Bozo' has 17 letters
The string 'Know-Nothing-Bozo' has 17 letters
The string 'Refracto-Nullifiers' has 19 letters
The string 'Semi-Conditionally' has 18 letters
The string 'Stagyar-zil-Doggo' has 17 letters
The string 'Stagyar-zil-Doggo,' has 18 letters
The string 'Stagyar-zil-Doggo.' has 18 letters
The string 'Streetmentioner's' has 17 letters
The string 'Streetmentioner's' has 17 letters
The string 'Telecommunications.' has 19 letters
The string 'Under-structured,' has 17 letters
The string 'aggressive-looking' has 18 letters
The string 'astrally-minded!"' has 17 letters
The string 'baggage-retrieval' has 17 letters
The string 'bloody-whatsiting' has 17 letters
The string 'bloodymindedness.' has 17 letters
The string 'blurglecruncheon,' has 17 letters
The string 'cellophane-windowed' has 19 letters
The string 'ceramo-teak-bonded' has 18 letters
The string 'change-of-address' has 17 letters
The string 'condescension,"...' has 18 letters
The string 'conspirationally.' has 17 letters
The string 'conversationally.' has 17 letters
The string 'dematerialization' has 17 letters
The string 'ecstasy-inducingly' has 18 letters
The string 'editor-in-chief's' has 17 letters
The string 'enthusiastically.' has 17 letters
The string 'enthusiastically.' has 17 letters
The string 'environmentalists' has 17 letters
The string 'environmentalists' has 17 letters
The string 'environmentalists' has 17 letters
The string 'epistemologically' has 17 letters
The string 'establishment..."' has 17 letters
The string 'feet/second/second,' has 19 letters
The string 'five-million-year' has 17 letters
The string 'four-dimensionally' has 18 letters
The string 'frankness,"There's' has 18 letters
The string 'great-grandchildren' has 19 letters
The string 'hypermathematics,' has 17 letters
The string 'hypermathematics,' has 17 letters
The string 'indistinguishable' has 17 letters
The string 'indistinguishable' has 17 letters
The string 'indistinguishable' has 17 letters
The string 'interior-designed' has 17 letters
The string 'land!!!!!!!!!!!!!!!' has 19 letters
The string 'maximegalacticans,' has 18 letters
The string 'methane-breathing' has 17 letters
The string 'microprocessors,"' has 17 letters
The string 'mind-witheringly,' has 17 letters
The string 'misunderstanding.' has 17 letters
The string 'mugging-expensive' has 17 letters
The string 'multi-dimensional' has 17 letters
The string 'multi-dimensional' has 17 letters
The string 'multi-dimensional' has 17 letters
The string 'multi-dimensional' has 17 letters
The string 'multi-dimensional' has 17 letters
The string 'multi-dimensional' has 17 letters
The string 'multi-dimensional' has 17 letters
The string 'one-and-a-half-ton' has 18 letters
The string 'quantum-mechanical' has 18 letters
The string 'relationships..."' has 17 letters
The string 'reverse-engineered' has 18 letters
The string 'self-preservation' has 17 letters
The string 'seventy-millimetre' has 18 letters
The string 'slower-than-light' has 17 letters
The string 'spaceship-stripping' has 19 letters
The string 'super-intelligent' has 17 letters
The string 'super-intelligent' has 17 letters
The string 'suspicious-looking' has 18 letters
The string 'telecommunications' has 18 letters
The string 'tense-corrections' has 17 letters
The string 'thousand-mile-long' has 18 letters
The string 'three-dimensional' has 17 letters
The string 'three-dimensional' has 17 letters
The string 'three-dimensional' has 17 letters
The string 'three-dimensional' has 17 letters
The string 'touch-sensitive-you' has 19 letters
The string 'well-disposedness' has 17 letters

An issue for you to think of: Try to understand the order of operations with nested loops. Why does the loop above not print output ordered by increasing string length, first printing all strings of length 17, then 18, 19,…? Can you think of another way of implementing this without the inner loop?

File handles are iterables#

As you can see from the first code block in this session, loops can iterate over lines in a file.

Note, though, that file handles have states, that is, they ‘remember where (in the file) they are’. For this reason, you have to reset the state of the file handle if you want to loop over file handles multiple times, using file.seek(0).

unsorted_file.seek(0)
print("The first 20 lines of the book are:\n")
for line in range(20):
    print(unsorted_file.readline())

#unsorted_file.seek(0) (use this command to reset the file handle)
print("\nBut if we loop over the file contents again, we continue where we left off:\n")
#this doesn't do what you think it might:
for line in range(20):
    print(unsorted_file.readline())
The first 20 lines of the book are:

THE ULTIMATE 

HITCHHIKER'S GUIDE 



Complete &amp; Unabridged 





Contents: 





Introduction: A Guide to the Guide 

The Hitchhiker's Guide to the Galaxy 

The Restaurant at the End of the Universe 

Life, the Universe and Everything 

So Long, and Thanks for All the Fish 

Young Zaphod Plays It Safe 

Mostly Harmless 

Footnotes 








But if we loop over the file contents again, we continue where we left off:

Introduction: A GUIDE TO 

THE GUIDE 





Some unhelpful remarks from the author 



The history of The Hitchhiker's Guide to the Galaxy is now so 

complicated that every time I tell it I contradict myself, and whenever 

I do get it right I'm misquoted. So the publication of this omnibus 

edition seemed like a good opportunity to set the record straight - or 

at least firmly crooked. Anything that is put down wrong here is, as far 

as I'm concerned, wrong for good. 



The idea for the title first cropped up while I was lying drunk in a 

field in Innsbruck, Austria, in 1971. Not particularly drunk, just the 

sort of drunk you get when you have a couple of stiff Gossers after 

not having eaten for two days straight, on account of being a 

penniless hitchhiker. We are talking of a mild inability to stand up. 



I was traveling with a copy of the Hitch Hiker s Guide to Europe by 

Sorting, reverse sorting, sorting by key#

Sorting elements in a sequence or collection of objects is essential to many use-cases of programming. Luckily, the functionality of sorting functions in Python is much more versatile than we have seen thus far:

- sort() and sorted()#

As seen above, sort() modifies the original sequence it was passed. To sort sequences or collections without disturbing the original sequence of the object, use the sorted() function. Sorted() returns an ordered copy of the sequence passed as its argument:

x = [3,5,1,2,4]
x_sorted = sorted(x)
print(x)
print(x_sorted)
[3, 5, 1, 2, 4]
[1, 2, 3, 4, 5]

- Sorting order#

The keyword reverse = True can be passed to both sort() and sorted() to reverse the sorting order from the default sorting order, which is ascending (numerically or alphabetically).

Both sort() and sorted() can also be passed a key parameter, which can be used to specify alternative sorting orders. The key parameter should be a function that takes a single argument (the respective element of the to-be-sorted sequence) and returns a key (the value the function returns for the passed element) to use for sorting purposes.

Notice that we here pass a function as an argument to a function!

For instance, you may want to ignore capitalization when sorting list elements alphabetically:

x = ["A","d","C","b","e"]
print(sorted(x))
print(sorted(x, key = str.lower))
['A', 'C', 'b', 'd', 'e']
['A', 'b', 'C', 'd', 'e']

- Sorting dictionaries by key or value#

Dictionaries are mappings between keys and values. As such, they are not intrinsically ordered. However, sometimes we may want to sort dictionaries into a sequence of ordered elements, e.g., when trying to read out the mapping between students’ names and grades by order of their first/last name (A-Z) or grade (1-6 in the German system).

By default, when applying the sorted() function, dictionaries are sorted by their keys. In fact, just feeding a dictionary straight to the sorted() function will simply return a sorted list of its keys.

#initialize dictionary (see session04)
first_names = ["Arthur", "Ford", "Zaphod", "Marvin"]
last_names  = ["Dent", "Prefect", "Beeblebrox", ""]
lifeform    = ["Human", "Betelgeusian", "Betelgeusian", "Android"]
feature     = ["Worried","Endlessly broad-minded","Two-headed","Paranoid"]

notebook    = {"First name" : first_names, 
               "Last name" : last_names, 
               "Life form" : lifeform, 
               "Distinct feature" : feature}
print(sorted(notebook)) #just returns sorted list of keys
['Distinct feature', 'First name', 'Last name', 'Life form']

Option for sorting dictionary and returning a list sorted by key: Using the dictionary method items(), we receive a dictionary view object, a list of tuples containing all key-value pairs (see also the methods values() and keys(), session 04). Calling sorted() on this object will return a list sorted by the dictionary’s keys.

for i in sorted(notebook.items()):
    print(i)
('Distinct feature', ['Worried', 'Endlessly broad-minded', 'Two-headed', 'Paranoid'])
('First name', ['Arthur', 'Ford', 'Zaphod', 'Marvin'])
('Last name', ['Dent', 'Prefect', 'Beeblebrox', ''])
('Life form', ['Human', 'Betelgeusian', 'Betelgeusian', 'Android'])

Option for sorting dictionary and retuning a list sorted by value: To sort a dictionary by value, we need to use a key parameter in the sorted() function that specifies which value of the dictionary elements to sort over.

  • Remember that the key parameter should be a function. But there is no in-built function for getting dictionary values…

  • Instead, a lambda function may be used to specify the sort key. Lambda functions are anonymous functions, that is, functions that are never defined with a name. They are useful when the function is only required for a short time/single application.

  • Their syntax is:

         lambda arguments: expression
    
  • This is akin to a function defined as:

      def function(arguments):
          return expression
    
  • Lambda functions can have any number of arguments but only one expression. That expression is evaluated and returned.

#using a lambda function to sort by value 
for i in sorted(notebook.items(),key = lambda x: x[1]):
    print(i)
('First name', ['Arthur', 'Ford', 'Zaphod', 'Marvin'])
('Last name', ['Dent', 'Prefect', 'Beeblebrox', ''])
('Life form', ['Human', 'Betelgeusian', 'Betelgeusian', 'Android'])
('Distinct feature', ['Worried', 'Endlessly broad-minded', 'Two-headed', 'Paranoid'])
  • Anonymoyus lambda function can be useful in many other ways. Notice that you should think of functions (anonymous or not) as first-order citizens that you can pass to functions or loop over, as is done here:

# create a list of functions (anonymous or named)
# then apply each function to a given value
list_of_functions = [lambda x: x+1, lambda x: x*2, round]
print([f(4.2) for f in list_of_functions])
[5.2, 8.4, 4]
  • Alternatively, the operator module has convenience functions to fetch items from an operand, which can be any sequence or collection of elements:

#sorts by value, same as the anonymous function above
from operator import itemgetter
for i in sorted(sorted(notebook.items(), key = itemgetter(1))):
    print(i)
('Distinct feature', ['Worried', 'Endlessly broad-minded', 'Two-headed', 'Paranoid'])
('First name', ['Arthur', 'Ford', 'Zaphod', 'Marvin'])
('Last name', ['Dent', 'Prefect', 'Beeblebrox', ''])
('Life form', ['Human', 'Betelgeusian', 'Betelgeusian', 'Android'])

While loops, or “Anything you can do, for can do better”?#

We saw in an earlier session that for loops and while loops can be used to achieve the same outcome. So how should you decide which loop to choose in the first place?

  • While loops continue until a condition is False. For loops iterate over collections, iterable objects or generator functions.

  • As such, for loops are the natural choice for data structures that are already iterable.

  • While loops can be used on data structures that are not iterable.

  • While loops may also be prefered if you do not know the number of required iterations beforehand.

  • Many dynamic applications (dynamic website, games, robots, … ) would use something like a while loop to constantly scan for a user’s input or changes in the environment.

from random import randrange

playlist = ["Let It Be",
            "Hey Jude",
            "Here Comes The Sun",
            "Something",
            "Taxman",
            "Eleanor Rigby",
            "Yellow Submarine",
            "Love You Do"]

command = "start"

print("Please choose an  option: \n 'repeat' -- repeat current song \n 'stop' -- stop playback \n other/no input -- continue playback\n")

while command !="stop":
    if command == "repeat":
        print("Repeating song:", current_song)
    else:
        current_song = playlist[randrange(7)] #on each execution, randrange picks a random integer from the specified range, here between 0 and 6
        print("Now playing: ", current_song ) 
    command = input()
print("Playback stopped.")   
Please choose an  option: 
 'repeat' -- repeat current song 
 'stop' -- stop playback 
 other/no input -- continue playback

Now playing:  Let It Be
quit
Now playing:  Eleanor Rigby
stop
Playback stopped.

Notes on code efficiency and maintainability#

Optimization#

For every computable problem or task, there are infinitely many programs which solve it; they will differ in memory and time usage. You may not care much about this for now, while working on toy examples with little computational demands. But if you get used to writing reasonably efficient code now, you will have a much easier time later!

What does it mean for code to be efficient? Some principles:

  • Avoid creating unnecessary objects. Do you really need that value stored in a variable?

  • Conversely, avoid computing the same thing twice by storing the value in a variable. Do you compute the same value on every execution of a loop? Why not save it in a variable?

  • Avoid running costly operations (such as file I/O, search) multiple times. Are you reading the same file at various places in your code? Why?

  • Use functions (and classes, see later sessions) to generalize and modularize your code. Avoid declaring variables with global scope to prevent unnecessary clutter. Will you really need that variable in your for loop again later on? Probably not…

An extreme example:

for i in range (2): #for i in range 0-1
    unsorted_file = open("hitchhikers_guide_full.txt", "r") #read in the file
    x = unsorted_file.readline().split() #read the first line, split into list of words
    global y #create global variable
    y = x[i] #save i-th word in list x in variable y
    print(y) #print variable
THE
ULTIMATE

The same thing, but more efficiently:

for i in range (2): #for i in range 0-1
    unsorted_file.seek(0) #reset file handle
    print(unsorted_file.readline().split()[i]) #print i-th word in first line
THE
ULTIMATE

Maintainability#

While code efficiency is about reducing computational complexity, code maintainability is about reducing complexity for (human) programmers.

Principles for code maintainability include:

  • Comment your code, explain user-defined functions’ behaviors, and use informative variable + function names to ensure your code is understandable to others

  • Don’t copy and paste code, use functions and loops.

  • Encapsulate elements that are subject to change (e.g., user input, file I/O) and those that stay the same (e.g., functions, classes). This makes it easier to identify adaptable components.

  • Minimize coupling between different code components. This makes your code more changeable.