Key Ideas
The key ideas for this part centre on basic exception handling and functional programming.
Exceptions
The core exception handling structure is shown by this example:
import random
try:
a = 1/random.random()
except:
a = 0
finally: # Stuff that definitely must happen.
if a == None: a = -1
print("Done")
If you know what exceptions are likely (list), you can catch each and respond to
them differently. You can also catch generic Exception
:
import random
try:
a = 1/random.random()
except ZeroDivisionError:
a = 0
except Exception:
a = -1
finally: # Stuff that definitely must happen.
if a == None: a = -1
print("Done")
If for some reason you need to end the program, you can do so with this call:
sys.exit()
which takes an optional int error code as an argument and returns it to the operating system.
Functional programming
In Python, a great deal of the functional elements fall around functions that use or apply other functions, which they take in as arguments. Here are some of the major examples.
The sorted
function can take in a function as a "key
" on which to search. The key function is
called and passed each item in the iterable container to be sorted. Note that the key should be
given as function_name
not usually function_name()
(which actually calls the method
there and then to return something).
new_list = sorted(iterable, key=function_name, reverse=False)
The map(function, iterable, ...)
function takes in a function and iterable container (list, tuple, etc.), and applies the
function to each element. For example:
for value in map(ord, ["A","B","C"]): # ord returns ACSII numbers
print(value)
One option with map
is to pass in a short 'headless' (nameless) or 'anonymous' function. Anonymous functions are
denoted with the keyword lambda
. Amongst other things, this allows multiple containers to be processed.
for square in map(lambda x: x**2, [1,2,3,4,5]):
print(str(square)) # 1, 4, 9, 16, 25
for added in map(lambda x,y: x+y, [1,2,3,4,5],[1,2,3,4,5]):
print(str(added)) # 2, 4, 6, 8, 10
b = "fire walk with me".split(" ")
for a in map(lambda x: x[::-1], b):
print(a) # erif klaw htiw em
The itertools library includes a variety of methods that work in similar ways, including some that aggregate the results of operations across the iterable in some way.
We can set up lists in a similar way. In 'list comprehensions' the function is written directly into the list setup. These tend to be relatively simple expressions.
listA = [1,2,3,4]
Can also be used to apply a method to everything in a list.
listB = [2*x for x in listA]
print(listB) # [2, 4, 6, 8]
listB = [2* x for x in range(1:5)]
print(listB) # [2, 4, 6, 8]
listB = [2*x for x in listA if x % 2 == 0] # Even numbers only
print(listB) # [4, 8]
listA = ['A','B','C']
listB = [ord(x) for x in listA]
print(listB) # [65, 66, 67]
They can also be used like a nested loop structure:
listA = [(x, y) for x in [1,2,3] for y in ["a","b","c"]]
print(listA)
[(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c'),
(3, 'a'), (3, 'b'), (3, 'c')]
As list comprehensions generate all the data at first execution and then copy it in, for very large lists, use generator expressions (the same, but with parentheses rather than square brackets) which generates items one at a time, as needed, and are therefore more memory efficient.
Generator functions are similar in that they generate a value at a time. They return a value at yield
and
wait for the next call. For example:
def count():
a = 0
while True:
yield a # Returns control and waits next call.
a = a + 1
b = count()
print (next(b))
print (next(b))
Where the condition is just set to True
they will generate an infinite sequences.
They must be attached to a variable so the same instance is called each time and the method-level variables aren't wiped; this doesn't work:
print (next(count()))