Das with Statement

[1]:
def f():
    l = [1, 2, 3]
[2]:
f()
[3]:
def f():
    try:
        openfile = open('/etc/passwd')
    except ValueError as e:
        # hanlde err
        raise
    finally:
        openfile.close()
f()
[4]:
def f():
    with open('/etc/passwd') as openfile:
        # do soemthing with openfile

        # no closing necessary
        pass  # Python requires me to write at least one syntactically correct statement

import zipfile

[5]:
import zipfile
[6]:
zf = zipfile.ZipFile('demo.zip')

Read Contents …

[7]:
zf.namelist()
[7]:
['tmp/zipdemo/', 'tmp/zipdemo/group', 'tmp/zipdemo/passwd']

Extract One Member

[8]:
extracted_name = zf.extract(member='tmp/zipdemo/group', path='/tmp/my-extraction-directory')
extracted_name
[8]:
'/tmp/my-extraction-directory/tmp/zipdemo/group'

All in One, Using with

[9]:
with zipfile.ZipFile('demo.zip') as zf:
    print(zf.namelist())
    print(zf.extract(member='tmp/zipdemo/group', path='/tmp/my-extraction-directory'))
['tmp/zipdemo/', 'tmp/zipdemo/group', 'tmp/zipdemo/passwd']
/tmp/my-extraction-directory/tmp/zipdemo/group

Classes

Question 31

Select the true statements:

(select all that apply)

    1. The class keyword marks the beginning of the class definition

    1. An object cannot contain any references to other objects

    1. A class may define an object

    1. A constructor is used to instantiate an object

    1. An object variable is a variable that is stored separately in every object

[10]:
class Person:
    pass
[11]:
p = Person()       # instantiation
p.first = 'Joerg'
p.last = 'Faschingbauer'
p.address = 'Prankergasse 33, 8020 Graz'
[12]:
class Person:
    def __init__(self, first, last, address):
        self.first = first
        self.last = last
        self.address = address
p = Person('Joerg', 'Faschingbauer', 'Prankergasse 33, 8020 Graz')    # instantiation
[13]:
p.first
[13]:
'Joerg'
[14]:
isinstance(p, Person)
[14]:
True
[15]:
isinstance(p, str)
[15]:
False
[16]:
p.__dict__
[16]:
{'first': 'Joerg',
 'last': 'Faschingbauer',
 'address': 'Prankergasse 33, 8020 Graz'}

Inheritance

Question 32

Select the true statements:

(select all that apply)

    1. Inheritance means passing attributes and methods from a superclass to a subclass

    1. issubclass(class1, class2) is an example of a function that returns True if class2 is a subclass of class1

    1. Multiple inheritance means that a class has more than one superclass

    1. Polymorphism is the situation in which a subclass is able to modify its superclass behavior

    1. A single inheritance is always more difficult to maintain than a multiple inheritance

[17]:
class Employee(Person):
    def __init__(self, first, last, address, salary):
        self.salary = salary
        super().__init__(first, last, address)
[18]:
e = Employee('Selina', 'Orgl', 'Somewhere 666, 8010 Graz', 2000)    # instantiation
[19]:
e.first
[19]:
'Selina'
[20]:
e.salary
[20]:
2000
[21]:
e.__dict__
[21]:
{'salary': 2000,
 'first': 'Selina',
 'last': 'Orgl',
 'address': 'Somewhere 666, 8010 Graz'}
[22]:
isinstance(e, Employee)
[22]:
True
[23]:
isinstance(e, Person)
[23]:
True
[24]:
issubclass(Employee, Person)
[24]:
True

Functionality: Methods

[25]:
import time

class Person:
    def __init__(self, first, last, address):
        self.first = first
        self.last = last
        self.address = address
        self.birth = time.time()
    def die(self):
        self.death = time.time()
    def lifetime(self):
        return self.death - self.birth

Employee inherits everything but salary

[26]:
class Employee(Person):
    def __init__(self, first, last, address, salary):
        self.salary = salary
        super().__init__(first, last, address)
[27]:
joerg = Person('Joerg', 'Faschingbauer', 'Prankergasse 33, 8020 Graz')
[28]:
joerg.birth
[28]:
1622102062.4778333

joerg.die() joerg.death

[29]:
joerg.die()
joerg.lifetime()
[29]:
0.019427776336669922
[30]:
selina = Employee('Selina', 'Orgl', 'Somewhere 666, 8010 Graz', 2000)
[31]:
selina.die()
selina.lifetime()
[31]:
0.009679079055786133
[32]:
daniel = Employee('Daniel', 'Ortner', 'Blah 42', 1000)
type(daniel)
[32]:
__main__.Employee
[33]:
isinstance(daniel, Employee)
[33]:
True
[34]:
isinstance(daniel, Person)
[34]:
True

Class Attributes vs. Instance Attributes (not Variables)

Instance Attributes

[35]:
daniel.first
[35]:
'Daniel'
[36]:
selina.first
[36]:
'Selina'
[37]:
joerg.first
[37]:
'Joerg'

Class Attributes

How many Employees exist?

[38]:
class Employee(Person):
    num_employees = 0   # class attribute
    def __init__(self, first, last, address, salary):
        self.salary = salary
        Employee.num_employees += 1    # class attribute can be accessed via class Employee
        super().__init__(first, last, address)
    def die(self):
        '''Acts like Person.die(), and in addition keeps track of num. employees'''
        self.num_employees -= 1  # class attributes can be access via any object of the class
        super().die()
[39]:
selina = Employee('Selina', 'Orgl', 'Somewhere 666, 8010 Graz', 2000)
daniel = Employee('Daniel', 'Ortner', 'Blah 42', 1000)
[40]:
Employee.num_employees
[40]:
2
[41]:
selina.die()
[42]:
Employee.num_employees
[42]:
2

Public, Protected, Private

“Private” is actually only name mangling

[43]:
class Person:
    def __init__(self, first, last, address):
        self.__first = first
        self.__last = last
        self.__address = address
p = Person('Joerg', 'Faschingbauer', 'Prankergasse 33, 8020 Graz')
[44]:
try:
    p.__first
except Exception as e:
    print(e)
'Person' object has no attribute '__first'
[45]:
p.__dict__
[45]:
{'_Person__first': 'Joerg',
 '_Person__last': 'Faschingbauer',
 '_Person__address': 'Prankergasse 33, 8020 Graz'}
[46]:
p._Person__first
[46]:
'Joerg'

This is maybe “Protected”?

[47]:
class Person:
    def __init__(self, first, last, address):
        self._first = first
        self._last = last
        self._address = address
p = Person('Joerg', 'Faschingbauer', 'Prankergasse 33, 8020 Graz')
[48]:
p.__dict__
[48]:
{'_first': 'Joerg',
 '_last': 'Faschingbauer',
 '_address': 'Prankergasse 33, 8020 Graz'}

Properties

[49]:
class Person:
    def __init__(self, first, last, address):
        self._first = first
        self._last = last
        self._address = address
    @property
    def first(self):
        return self._first
    @first.setter
    def first(self, name):
        'Only allow Persons with firstname Joerg to be renamed'
        if self._first == 'Joerg':
            self._first = name
        else:
            raise RuntimeError('nix rename')
p = Person('Joerg', 'Faschingbauer', 'Prankergasse 33, 8020 Graz')
[50]:
p.first
[50]:
'Joerg'
[51]:
p.first = 'Eugenie'

Functions, Positional and Keyword Arguments

[52]:
def velocity(length_m, time_s, do_debug):
    val = length_m/time_s
    if do_debug:
        print('velocity=', val)
    return val

Positional Arguments

[53]:
velocity(5, 10, True)
velocity= 0.5
[53]:
0.5
[54]:
velocity(10, 5, False)
[54]:
2.0

Keyword Arguments

[55]:
velocity(time_s=10, length_m=5, do_debug=False)
[55]:
0.5

Mixing Positional and Keyword Arguments

[56]:
velocity(5, do_debug=True, time_s=10)    # keyword args only after positional
velocity= 0.5
[56]:
0.5

The range() Function

[57]:
row = ['Language', 'bloh', '', '666', '']
[58]:
len(row)
[58]:
5
[59]:
for i in range(2, len(row)):
    print(i)
2
3
4
Was ist ``range()`` ueberhaupt???
[60]:
r = range(3)   # same as range(0,3)
r
[60]:
range(0, 3)
[61]:
it = iter(r)
[62]:
next(it)
[62]:
0
[63]:
next(it)
[63]:
1
[64]:
next(it)
[64]:
2
[65]:
try:
    next(it)
except StopIteration:
    print('there is nothing more')
there is nothing more

This is exactly the same a using the for loop to iterate over a range (or anything that is iterable)

[66]:
for element in range(3):
    print(element)
0
1
2

Functional Programming, Iteration, yield, map(), filter(), …

map()

[67]:
l = ['1.2', '3.4', '666.42']

Convert strings to floats:

[68]:
l_float = []
for element in l:
    l_float.append(float(element))
l_float
[68]:
[1.2, 3.4, 666.42]

Do the same using a list comprehension

[69]:
l_float = [float(element) for element in l]
l_float
[69]:
[1.2, 3.4, 666.42]

Do the same using map()

[70]:
l_float = map(float, l)
for el in l_float:
    print(el)
1.2
3.4
666.42

Do the same using a generator expression

[71]:
l_float = (float(element) for element in l)
l_float
[71]:
<generator object <genexpr> at 0x7fd9881b04a0>
[72]:
for el in l_float:
    print(el)
1.2
3.4
666.42

Iterable

list is iterable

[73]:
l = [0, 1, 2]
for element in l:
    print(element)
0
1
2

str is iterable

[74]:
s = 'abc'
for element in s:
    print(element)
a
b
c

range() objects are iterable

[75]:
r = range(3)
for element in r:
    print(element)
0
1
2

dict is iterable

[76]:
d = {1:'one', 2: 'two'}
for element in d:
    print(element)
1
2

list(), and iterable?

What does the list constructor do with its parameter if you pass it one?

[77]:
l = list()
l
[77]:
[]
[78]:
r = range(3)
list(r)
[78]:
[0, 1, 2]
[79]:
list('abc')
[79]:
['a', 'b', 'c']
[80]:
l = [1,2,3]
for element in l:
    print(element)
1
2
3
[81]:
list(l)
[81]:
[1, 2, 3]

Tuple Unpacking and the Rest

[82]:
l = [1, 2, 3, 4, 5, 6]
a, b, *rest = l
print(a, b, rest)
1 2 [3, 4, 5, 6]

Decorators, etc.

[83]:
import functools

def debug(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f'{func.__name__} called: args={args}, kwargs={kwargs}')
        return func(*args, **kwargs)
    return wrapper

# add = debug(add)
@debug
def add(a, b):
    'das ist der docstring der extrem komplexen funktion add()'
    return a+b

@debug
def square(a):
    return a**2

print(f'function add has name {add.__name__}, and is documented like so: {add.__doc__}')
print('add (args)', add(1, 2))
print('add (kwargs)', add(a=1, b=2))
print('square', square(10))

function add has name add, and is documented like so: das ist der docstring der extrem komplexen funktion add()
add called: args=(1, 2), kwargs={}
add (args) 3
add called: args=(), kwargs={'a': 1, 'b': 2}
add (kwargs) 3
square called: args=(10,), kwargs={}
square 100

NoneType and None

Remember NULL in C?

[84]:
a = None
[85]:
type(a)
[85]:
NoneType
[86]:
d = { 1:'one', 2:'two' }
d[1]
[86]:
'one'

Index Operator crashes if key not there …

[87]:
try:
    d[3]
except KeyError as e:
    print(type(e), e)
<class 'KeyError'> 3
[88]:
value = d.get(3)
print(value)
None
[89]:
value = d.get(3)
if value is None:
    print('not there')
else:
    print(value)
not there
[90]:
def f():
    pass   # does nothing, not even return anything
result = f()
print(result)
None

File I/O

[91]:
f = open('testfile.txt')
[92]:
f.read()
[92]:
'zeile 1\nzeile 2\nzeile 3\n'
[93]:
f.seek(0)
[93]:
0
[94]:
f.readline()
[94]:
'zeile 1\n'
[95]:
f.readline()
[95]:
'zeile 2\n'
[96]:
f.readline()
[96]:
'zeile 3\n'
[97]:
f.readline()   # EOF
[97]:
''
[98]:
f.seek(0)
[98]:
0

Files are iterable

[99]:
for line in f:
    print(line)
zeile 1

zeile 2

zeile 3

Iteration, yield, Recursion

Recursion

[106]:
def nth_fibo(n):
    if n < 1:
        raise RuntimeError('No fibonacci number below 1')
    if n == 1 or n == 2:
        return 1
    return nth_fibo(n-1) + nth_fibo(n-2)
[107]:
nth_fibo(7)
[107]:
13
[108]:
nth_fibo(2)
[108]:
1
[113]:
try:
    nth_fibo(0)
except RuntimeError as e:
    print(e)
No fibonacci number below 1

Iteratively Calculating Fibonacci Numbers -> yield

[119]:
N = 10 - 2  # 2 to accomodate first and second

first, second = 1, 1
print(first)
print(second)
while N > 0:
    N -= 1
    third = first + second
    print(third)
    first, second = second, third
1
1
2
3
5
8
13
21
34
55
[121]:
first, second = 1, 1
print(first)
print(second)
for i in range(8):
    third = first + second
    print(third)
    first, second = second, third
1
1
2
3
5
8
13
21
34
55

Function to calculate a list of first N fibonacci numbers …

[122]:
def fibo(n):
    'Returns a list of first n fibonacci numbers'
    nums = []
    first, second = 1, 1
    nums.append(first)
    nums.append(second)
    for i in range(n-2):
        third = first + second
        nums.append(third)
        first, second = second, third
    return nums

# user can do what she wants with those numbers. for example, print them
for n in fibo(10):
    print(n)
1
1
2
3
5
8
13
21
34
55
[123]:
def fibo(n):
    'Returns an iterable that generates the first n fibonacci numbers'
    first, second = 1, 1
    yield first
    yield second
    for i in range(n-2):
        third = first + second
        yield third
        first, second = second, third

# user can do what she wants with those numbers. for example, print them
for n in fibo(10):
    print(n)
1
1
2
3
5
8
13
21
34
55
[124]:
f = fibo(4)
f
[124]:
<generator object fibo at 0x7fd9801cec10>
[125]:
it = iter(f)
[126]:
next(it)
[126]:
1
[127]:
next(it)
[127]:
1
[128]:
next(it)
[128]:
2
[129]:
next(it)
[129]:
3
[131]:
try:
    next(it)
except StopIteration:
    pass
[ ]:

map(), filter(), zip(), enumerate()

map(), and several other ways to do the same

[132]:
l = ['1.2', '3.4', '666.0']

Use a list comprehension to transform l to floats

[134]:
lf = [float(element) for element in l]
lf
[134]:
[1.2, 3.4, 666.0]

Use map() to do the same

[135]:
lf = map(float, l)
lf
[135]:
<map at 0x7fd9801b8d60>

Ah, have to iterate over the iterable to see the generated items

[136]:
for e in lf:
    print(e)
1.2
3.4
666.0

Use a generator expression to do the same

[138]:
lf = (float(e) for e in l)
lf
[138]:
<generator object <genexpr> at 0x7fd9801b4900>
[139]:
for e in lf:
    print(e)
1.2
3.4
666.0

zip()

[140]:
l1 = [1, 2, 3, 4]
l2 = ['one', 'two', 'three', 'four']
[142]:
zipped = zip(l1, l2)
zipped
[142]:
<zip at 0x7fd98014cd40>
[143]:
for element in zipped:
    print(element)
(1, 'one')
(2, 'two')
(3, 'three')
(4, 'four')
[144]:
dict(zip(l1, l2))
[144]:
{1: 'one', 2: 'two', 3: 'three', 4: 'four'}

What if both are not of equal lengths?

[145]:
l1 = [1, 2, 3, 4, 5]
l2 = ['one', 'two', 'three', 'four']
[146]:
for l, r in zip(l1, l2):
    print(l, r)
1 one
2 two
3 three
4 four

filter(), and several other methods to do the same

[147]:
numbers = range(10)

We want even numbers only:

[148]:
def even(n):
    return n%2 == 0
for e in filter(even, numbers):
    print(e)
0
2
4
6
8
[150]:
for e in filter(lambda n: n%2 == 0, range(10)):
    print(e)
0
2
4
6
8

Use a list comprehension to do the same

[158]:
[element for element in range(10) if element%2 == 0]
[158]:
[0, 2, 4, 6, 8]

Use a generator expression to achieve ultimate beauty and performance

[160]:
gen = (element for element in range(10) if element%2 == 0)
for e in gen:
    print(e)
0
2
4
6
8

enumerate()

[151]:
l = ['one', 'two', 'three', 'four']
for element in enumerate(l):
    print(element)
(0, 'one')
(1, 'two')
(2, 'three')
(3, 'four')
[152]:
l = ['one', 'two', 'three', 'four']
for sequenceno, s in enumerate(l):
    print(sequenceno, s)
0 one
1 two
2 three
3 four
[153]:
l = ['one', 'two', 'three', 'four']
for sequenceno, s in enumerate(l, 1):
    print(sequenceno, s)
1 one
2 two
3 three
4 four
[154]:
l = ['one', 'two', 'three', 'four']
for kv_pair in enumerate(l, 1):
    print(kv_pair)
(1, 'one')
(2, 'two')
(3, 'three')
(4, 'four')
[155]:
dict(enumerate(l, 1))
[155]:
{1: 'one', 2: 'two', 3: 'three', 4: 'four'}

OSError, errno

[164]:
import os
try:
    os.remove('/etc/passwd')
except PermissionError as e:
    print(e)
    print(f'errno is, btw, {e.errno}')
[Errno 13] Permission denied: '/etc/passwd'
errno is, btw, 13
[168]:
import errno
errno.EACCES
[168]:
13
[169]:
issubclass(PermissionError, OSError)
[169]:
True
[170]:
issubclass(FileNotFoundError, OSError)
[170]:
True

The platform Module, sys.path

[171]:
import platform
[175]:
platform.architecture()
[175]:
('64bit', 'ELF')
[178]:
platform.machine()
[178]:
'x86_64'
[183]:
platform.processor()
[183]:
'x86_64'
[186]:
platform.version()
[186]:
'#1 SMP Wed Apr 21 13:18:33 UTC 2021'
[188]:
platform.python_implementation()
[188]:
'CPython'
[190]:
platform.python_version_tuple()
[190]:
('3', '9', '4')

Module search path

[191]:
import sys
sys.path
[191]:
['/home/jfasch/work/jfasch-home/trainings/log/detail/2021-05-25',
 '/home/jfasch/work/openheating',
 '/home/jfasch/work/jfasch-home',
 '/home/jfasch/work/jfasch-home/trainings/log/detail/2021-05-25',
 '/usr/lib64/python39.zip',
 '/usr/lib64/python3.9',
 '/usr/lib64/python3.9/lib-dynload',
 '',
 '/home/jfasch/venv/homepage/lib64/python3.9/site-packages',
 '/home/jfasch/venv/homepage/lib/python3.9/site-packages',
 '/home/jfasch/venv/homepage/lib64/python3.9/site-packages/IPython/extensions',
 '/home/jfasch/.ipython']

os.path

[193]:
import os.path
[194]:
os.path.exists('/etc/passwd')
[194]:
True
[196]:
os.path.isfile('/etc/passwd')
[196]:
True
[197]:
os.path.isdir('/etc/passwd')
[197]:
False
[198]:
os.sep
[198]:
'/'
[200]:
my_path = '..' + os.sep + 'lib'
my_path
[200]:
'../lib'
[201]:
my_path = os.path.join('..', 'lib')
my_path
[201]:
'../lib'

Exceptions und so (assert())

[202]:
try:
    open('some-file-that-hopefully-does-not-exist')
except FileNotFoundError as e:
    the_exception = e
[203]:
the_exception
[203]:
FileNotFoundError(2, 'No such file or directory')
[204]:
isinstance(the_exception, FileNotFoundError)
[204]:
True
[205]:
the_exception.__class__
[205]:
FileNotFoundError
[207]:
the_exception.__class__.__bases__
[207]:
(OSError,)

Ah , OSError

[209]:
OSError.__bases__
[209]:
(Exception,)
[210]:
Exception.__bases__
[210]:
(BaseException,)
[211]:
BaseException.__bases__
[211]:
(object,)

And StopIteration? Where is that in the hierarchy?

[221]:
StopIteration.__bases__
[221]:
(Exception,)

AssertionError

“To assert” stands for “make sure that a condition holds”

[212]:
l = []
l.append(1)
l.append(2)
l.append(3)

Here the list must have three elements, we know that for sure.

Distrusting the list class (Guido is incompetent)

[216]:
assert len(l) == 3     # well, that precondition holds
[219]:
# being too stupid for distrust altogether (I appended three items, not four)
try:
    assert len(l) == 4
except AssertionError as e:
    print('Shit happened:', type(e))
Shit happened: <class 'AssertionError'>
[220]:
AssertionError.__bases__
[220]:
(Exception,)

Random Questions

[1]:
for i in range(1,3):
    print(i)
1
2
[2]:
my_list = [0 for i in range(1,3)]
my_list
[2]:
[0, 0]

Slicing

[3]:
s = 'abdefg'
s[1::2]
[3]:
'beg'
[5]:
s[1:]
[5]:
'bdefg'

OO

Note: self is the object

[8]:
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(2, 5)
print(p.x, p.y)
2 5

How to stringify an object?

[11]:
i = 42
print(i)
42
[13]:
str(i)
[13]:
'42'
[15]:
i.__str__()
[15]:
'42'

And class Point?

[16]:
p = Point(1, 2)
print(p)
<__main__.Point object at 0x7ffa9c2ac3a0>
[21]:
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __str__(self):
        return f'({self.x}, {self.y})'

p = Point(2, 5)
print(p)
(2, 5)

String Formatting

[22]:
formatstring = 'das ist ein ding: {}, und noch eins: {}'
[25]:
formatierter_string = formatstring.format(666, 'eugenie')
formatierter_string
[25]:
'das ist ein ding: 666, und noch eins: eugenie'
[28]:
formatstring = 'das ist ein ding: {a}, und noch eins: {b}'
formatstring.format(b=666, a='eugenie')
[28]:
'das ist ein ding: eugenie, und noch eins: 666'
[31]:
'das ist {a} ' + 'und {b} ist auch noch dabei'.format(a=666, b='eugenie')
[31]:
'das ist {a} und eugenie ist auch noch dabei'

import datetime

[44]:
'{a}'.format(a=1)
[44]:
'1'
[46]:
'{a:5}'.format(a=1)
[46]:
'    1'
[48]:
'{a:<5}'.format(a=1)
[48]:
'1    '
[49]:
'{a:>5}'.format(a=1)
[49]:
'    1'
[50]:
'{a:02}'.format(a=1)
[50]:
'01'