Python Introductory Course

Welcome to day 5:

  • Numpy II
  • Classes III
  • Exception Handling
  • Functions III (lambdas, as function arguments, callable)

Presenter Notes

Exercises : Day 5 - 1

  • Go through yesterday's slides about Numpy and practice creation and simple math for 1d arrays
  • Create an array with the values [2., 4., 4., 4., 5., 5., 7., 9.] Compute the standard deviation of this population (c.f. https://en.wikipedia.org/wiki/Standard_deviation). Do this once using the numpy function np.std and once doing the math using numpy math functionality
  • use numpy to compute an array of sine values for 20 stuetzstellen in [0, 2*PI) c.f. exercise 2 (day 2). Hint: You can use np.linspace for creating the array of stuetzstellen. Check help(np.linspace) and doctests for examples

Presenter Notes

Numpy II - 2(n)-d Arrays

 1 # 2-d array (matrix) 
 2 m = np.array([[1, 2], [3, 4]])   # array([[1, 2], [3, 4]])
 3 
 4 # check shape (dimensions)
 5 m.shape   # (2, 2)
 6 
 7 # identity matrix
 8 id = np.identity(2)  # array([[1., 0.], [0., 1.]])
 9 
10 # matrix multiplication
11 m.dot(id)   # array([[1., 2.], [3., 4.]])
12 
13 # transposition
14 m.transpose()  # array([[1, 3], [2, 4]])
15 
16 # matrix x vector 
17 v = np.array([1., 1.])
18 res1 = m.dot(v)  # array([3., 7.])
19 
20 # n-d arrays: NOTE: passed argument is the shape (not the values)
21 ndarr = np.ndarray((2,4,5))

Presenter Notes

Numpy II - linear algebra

• sub-module np.linalg with the standard linear algebra methods

• eigenvalues, decompositions, norms, inversion etc.

•based on BLAS and LAPACK

 1 m = np.array([[1, 2], [3, 4]])
 2 
 3 # eigen values
 4 np.linalg.eigvals(m)  # array([-0.37228132,  5.37228132])
 5 
 6 # inverse matrix
 7 np.linalg.inv(m)   # array([[-2. ,  1. ], [ 1.5, -0.5]])
 8 
 9 # determinant
10 np.linalg.det(m)  # -2.0000000000000004

Presenter Notes

Exercises : Day 5 - 2

  • create the rotation matrix M and the vector x of exercise 4 (day 1) as a Numpy array
  • multiply them and compare to the old result
  • compute the inverse of M
  • multiply the inverse of M with the resulting vector of task 2 above

Presenter Notes

Classes : magic methods

You may have wondered, how things like this are actually working:

  • my_list[5] and len(my_list)
  • collection iterations with in
  • np.array([1,2,3]) + np.array([5,6,7]) (the "+" operation)

→ special "magic" (aka "dunder") methods with double underscore defined in classes

Presenter Notes

Magic methods - __repr__, __add__, __len__

 1 # declare our own string class
 2 class OurString:
 3 
 4     # magic method to initiate object
 5     def __init__(self, string):
 6         self.string = string
 7 
 8     # defines what is shown in print
 9     def __repr__(self):
10         return 'Python-Kurs: {}'.format(self.string)
11 
12     # defines '+' operator
13     def __add__(self, other):
14         return self.string + other.string
15 
16     # 'length' of object -> Python's len(...) function
17     def __len__(self):
18       return len(self.string)
19 
20 os = OurString("Day-5")
21 progressing = OurString(" is progressing")
22 print(os) # Python-Kurs: Day-5
23 print(len(os)) # 5
24 print(os + progressing) # Day-5 is progressing

Presenter Notes

Magic Methods

  • useful to let standard python operators operate with and on your class
  • gives users of your code a "very native" Python feeling
  • aka "Operator Overloading"
  • similar to C++ operator but much less language overhead

Presenter Notes

Exercises : Day 5 - 3

  • The __getitem__ magic method implements element extraction for a class
  • E.g. for a list with variable name l, __getitem__(0) is called when we request the zeroth element with l[0]

  • Exercise: Copy the OurString class from above and implement the __getitem__(index) method to return the character at position index of the underlying string variable (self.string)

Presenter Notes

Exceptions

  • there can always be errors (IO, math, User Input etc...)
  • on error an exception occurs (you have seen many these days I guess)
  • ... breaks normal control flow, and makes its way upstreams in the call stack
  • if it is not handled somewhere upstreams, your program aborts
  • usually, you get a stack trace
  • pass information from "inner" part to "outer" part
  • can be catched in the code
  • ... decide there, if recovery is possible
  • exceptions are produced using raise Exception

Presenter Notes

Exception Handling : Example I

1 a = None
2 try:
3     a = 5 / 0 # this might be in a function called from here
4 except Exception as e:  # catching
5     print("Oh dear! Are you dividing by zero? Please go and read just any math book!")
6 finally:
7     print("Puh, we could get around that one...")
8     a = 5 / 1 # let us set a to something safe and continue!

Presenter Notes

Exception Handling : Example I Discussion

  • is something like this realistic?
  • do we want to protect each division against division by zero?
  • can we recover? Maybe with the help of the user?
  • at least we can try to terminate the program gracefully
  • good places to use: IO, user input

Presenter Notes

Exception Handling : Example II

1 fraction = { "denominator": 1, "numerater": 0}
2 try:
3     a = fraction["numerator"] / fraction["denominator"]
4 except Exception as e:  # catching
5     print("Oh dear! Are you dividing by zero? Please go and read just any math book!")
6 finally:
7     print("Puh, we could get around that one...")
8 a = 5 / 1

Who can see what is wrong here?

Presenter Notes

Broken Exception Handling

Landscape

Presenter Notes

Exceptions - With Care!

  • if you use exceptions, be very careful
  • cleanly separate the exceptions
  • catch specifically - no general catch!
  • raise the right exception (a builtin exception or a direct child)

Presenter Notes

Common Builtin Exceptions

  • for a complete list: https://docs.python.org/3/library/exceptions.html
  • ValueError: wrong value in a specified data type
  • TypeError: operation or function applied to object of inappropriate type
  • OSError: s.th. OS relate is wrong, e.g. IO. Has several Sub-Classes:
    • FileNotFoundError, FileExistsError
    • TimeoutError
    • ConnectionRefusedError
  • BaseException: Derive your own from here

Presenter Notes

Best Practices

  • never use exceptions for flow-control
  • handle exceptions at the level that knows how to handle them
  • do not expose implementation details with exceptions
  • document your exceptions

[https://eli.thegreenplace.net/2008/08/21/robust-exception-handling/]

Presenter Notes

Short excursion os.path

 1 import os
 2 
 3 # check if path exists
 4 os.path.exists(os.curdir)   # True
 5 
 6 # is it a file?
 7 os.path.isfile(os.curdir)   # False
 8 
 9 # is it a directory
10 os.path.isdir(os.curdir)    # True
11 
12 # list directory content
13 os.path.isfile(os.curdir)   # ['.git', '.idea', 'course', 'exercise_solutions', 'README.md', 'venv']
14 
15 # creation time of file
16 os.path.getctime('README.md')  # 1675866272.8470626
17 
18 # use time module to convert to GMT
19 import time
20 time.gmtime(os.path.getctime('README.md')) # time.struct_time(tm_year=2023, tm_mon=2, tm_mday=8, tm_hour=14, tm_min=24, tm_sec=32, tm_wday=2, tm_yday=39, tm_isdst=0)

Presenter Notes

Functions III

  • functions are very handy in python
  • can be treated as other objects
  • can be passed as argument to other function
  • can be returned from a function
  • can be defined ad hoc (lambda expression)

Presenter Notes

Functions as function argument

 1 def identity(x):
 2     return x
 3 
 4 def square(x):
 5     return x*x
 6 
 7 def apply(x, f):
 8     # check f to be a function
 9     if not callable(f):
10         raise ValueError("argument f needs to be callable")
11     # NOTE: here we return the evaluated function value (not the function)
12     return f(x)
13 
14 print(apply(4.0, square))  # 16.0
15 print(apply(2, identity))  # 2

Presenter Notes

Functions as function argument

 1 def identity(x):
 2     return x
 3 
 4 def square(x):
 5     return x*x
 6 
 7 # determine what to apply
 8 def choose_apply(method):
 9     if method == "identity":
10         # NOTE: here we return the function object
11         return identity
12     elif method == "square":
13         # NOTE: here we return the function object
14         return square
15 
16 print(choose_apply("square")(4.0))  # 16.0
17 print(choose_apply("identity")(2))  # 2

Presenter Notes

Ad Hoc functions (Lambda's)

• General syntax: lambda x: expression(x), x freely chooseable label

• Expression creates a function object

 1 # remember the sorting by passing a function:
 2 sorted([3, 5, -6, -1], key=abs)  # [-1, 3, 5, 6]
 3 
 4 # what if we want to sort by the modulus of 4? (need x and 4)
 5 sorted([3, 5, -6, -1], key=lambda x: x % 4)  # [5, -6, 3, -1]
 6 
 7 # also useful as return
 8 def my_pow(power):
 9     return lambda y: y**power
10 
11 print(my_pow(3)(2))  # 8
12 print(my_pow(5)(3))  # 243

Presenter Notes

Presenter Notes