Python Schulung 18. und 19.5.2020

Das sys Modul

[1]:
import sys
sys.argv
[1]:
['/home/jfasch/var/jfasch-home-venv/lib64/python3.8/site-packages/ipykernel_launcher.py',
 '-f',
 '/home/jfasch/.local/share/jupyter/runtime/kernel-57bd7d77-6e54-4dcd-b7a8-452f82f88569.json']

Multiline Strings und Docstrings

[2]:
s = 'hallo'
[3]:
s = '''hallo
welt
und
noch
was'''
[4]:
print(s)
hallo
welt
und
noch
was

Multiline Strings werden gerne zur Dokumentation verwendet:

[5]:
def f():
    '''das ist die Doku für diese Funktion

    Die Parameter sind ... aeh wir haben keine'''
    return 'hallo'
[6]:
f()
[6]:
'hallo'
[7]:
f.__doc__
[7]:
'das ist die Doku für diese Funktion\n    \n    Die Parameter sind ... aeh wir haben keine'

Im interaktiven Modus gibts die Funktion help(), die die Formatierung übernimmt

[8]:
help(f)
Help on function f in module __main__:

f()
    das ist die Doku für diese Funktion

    Die Parameter sind ... aeh wir haben keine

Datentypen (Ausflug)

Nachdem f ein Member __doc__ hat, ist f offenbar ein Objekt. Welches denn?

[9]:
type(f)
[9]:
function

Aha. Cool. f ist vom Typ function. Welche Typen hamma denn noch so?

[10]:
i = 42
type(i)
[10]:
int
[11]:
s = 'hello'
type(s)
[11]:
str
[12]:
l = [1, 'zwei', "drei"]
type(l)
[12]:
list

Objekte bieten Funktionalität: Methoden

[13]:
s.upper()
[13]:
'HELLO'
[14]:
l.append(4)
l.extend([5,'sechs'])
l
[14]:
[1, 'zwei', 'drei', 4, 5, 'sechs']

Aber zurueck zur funktion …

[15]:
f.__doc__
[15]:
'das ist die Doku für diese Funktion\n    \n    Die Parameter sind ... aeh wir haben keine'
[16]:
help(f)
Help on function f in module __main__:

f()
    das ist die Doku für diese Funktion

    Die Parameter sind ... aeh wir haben keine

Variablen und deren Unterbau

[17]:
a = 42
[18]:
print(a)
42

id: Objektidentität

[19]:
hex(id(a))
[19]:
'0x7fe9d8578dc0'
[20]:
type(a)
[20]:
int

Variablen sind Namen, die Objekte referenzieren. Objekte haben einen Typ, Namen nicht. Jetzt zum Beispiel wechselt die Variable a ihren Typ:

[21]:
a = 1.5
type(a)
[21]:
float

Das Objekt, auf das a nun zeigt, hat eine neue Identität.

[22]:
hex(id(a))
[22]:
'0x7fe9c4454d90'
[23]:
a = [1,2,3]
type(a)
[23]:
list

Ausflug “Pythonic”: Zuweisung mit Hilfe von “Tuple Unpacking”

[24]:
a, b, c = 1, 'zwei', 3.0
print(a,b,c)
1 zwei 3.0
[25]:
(a,b,c) = (1, 'zwei', 3.0)
print(a,b,c)
1 zwei 3.0
[26]:
t = (1,2,3)
t
[26]:
(1, 2, 3)

Tuples sind immutable

[27]:
try:
    t.append(4)
except AttributeError:
    print('tuples sind immutable: append() geht nur mit Listen')
tuples sind immutable: append() geht nur mit Listen

Ausdrucksstark weil kurz und trotzdem lesbar:

[28]:
a, b = b, a
print(a, b)
zwei 1

Assignment details, Referenzen (vgl, Folie 78): beide Namen a und b referenzieren das gleiche Objekt.

[29]:
a = 42
hex(id(a))
[29]:
'0x7fe9d8578dc0'
[30]:
b = a
hex(id(b))
[30]:
'0x7fe9d8578dc0'

Mutability am Beispiel list

[31]:
l1 = [1,2,3]
l2 = l1
print(l1)
print(l2)
[1, 2, 3]
[1, 2, 3]

l1 und l2 referenzieren das gleiche Objekt. Wenn man eine Variable verwendet, um es zu modifizieren, ist die Modifikation auch über die andere Variable sichtbar.

[32]:
l1.append(4)
print(l1)
[1, 2, 3, 4]
[33]:
l2
[33]:
[1, 2, 3, 4]

Integers haben unendliche Breite

Bis 64 Bit native CPU, ab dann wird in Software gerechnet.

[34]:
i = 42
type(i)
[34]:
int
[35]:
i = 10**2
i
[35]:
100
[36]:
i = 10**6
i
[36]:
1000000
[37]:
i = 2**64 - 1
i
[37]:
18446744073709551615
[38]:
i += 1
i
[38]:
18446744073709551616
[39]:
i = 2**1000
i
[39]:
10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376

Division und Floor Division

[40]:
i = 3/2
i
[40]:
1.5
[41]:
i = 3//2
i
[41]:
1

Konvertierung durch Konstruktoren

[42]:
i = 42
type(i)
[42]:
int
[43]:
s = '42'
type(s)
[43]:
str
[44]:
type(int)
[44]:
type
[45]:
s = str('abc')
s
[45]:
'abc'
[46]:
s = 'abc'  # einfacher
s
[46]:
'abc'
[47]:
s = str(42)
s
[47]:
'42'
[48]:
i = int(42)
i
[48]:
42
[49]:
i = int('42')
i
[49]:
42
[50]:
try:
    i = int('abc')
except ValueError:
    print('das war kein Integer in dem String')
das war kein Integer in dem String

Welchen Typ haben Typen?

42 ist vom Type int. int ist ein Name für etwas. Was ist dieses Etwas?

[51]:
print(int)
<class 'int'>
[52]:
int('42')
[52]:
42
[53]:
int = 1
print(int)
1
[54]:
try:
    int('42')
except TypeError:
    print('Hilfe, wir haben den int gelöscht')
Hilfe, wir haben den int gelöscht

Retten wir den int

[55]:
type(int)
[55]:
int
[56]:
type(42)
[56]:
int
[57]:
type(type(int))
[57]:
type
[58]:
int = type(42)
type(int)
[58]:
type
[59]:
int('42')  # uff
[59]:
42

list und Mutability

[60]:
l = [1,2,3]
print(id(l))
l
140641996429568
[60]:
[1, 2, 3]

append() und extend() ändern das Object in place

[61]:
l.append(4)
print(id(l))
l
140641996429568
[61]:
[1, 2, 3, 4]
[62]:
l.extend([5,6,7])
print(id(l))
l
140641996429568
[62]:
[1, 2, 3, 4, 5, 6, 7]

+ erzeugt ein neues Objekt (die Operanden bleiben unverändert)

[63]:
new_l = l + [8,9]
print(id(new_l))
print(new_l)
print(id(l))
print(l)
140641996429632
[1, 2, 3, 4, 5, 6, 7, 8, 9]
140641996429568
[1, 2, 3, 4, 5, 6, 7]

Was kann eine liste noch? (Bitte unbedingt die Dokumentation lesen.)

[64]:
7 in l
[64]:
True
[65]:
if 7 in l:
    print('hurra')
hurra
[66]:
l * 2
[66]:
[1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]
[67]:
'abc' * 2    # absolutes Killerfeature :-)
[67]:
'abcabc'
[68]:
l[3]
[68]:
4
[69]:
del l[0]
l
[69]:
[2, 3, 4, 5, 6, 7]

in ist durch sequentielle suche implementiert (dict ist besser, siehe unten)

[70]:
7 in l
[70]:
True

Index-Operator: per Position in der Liste

[71]:
l[5]
[71]:
7

Suche in Listen von Records ist umständlich (und langsam). Das geht mit Dictionaries besser.

[72]:
users = [('joerg', 'faschingbauer'), ('andreas', 'pfeifer')]
[73]:
for u in users:
    if u[1] == 'pfeifer':
        gefundener_user = u
        break
print(gefundener_user)
('andreas', 'pfeifer')

Tuple und Immutability

[74]:
t = (1,2,3)
t
[74]:
(1, 2, 3)
[ ]:

[75]:
t = tuple()
t
[75]:
()

Tuple mit einem Element? Straightforward würde man glauben, so:

[76]:
t = (1)
print(t)
type(t)
1
[76]:
int

What?

Die runden Klammern werden hier nicht als Tuple-Begrenzer interpretiert, sondern als Mittel, die Operator-Präzedenz zu overriden. Der folgende Ausdruck …

[77]:
i = 2**64 - 1
i
[77]:
18446744073709551615

… wird implizit interpretiert als …

[78]:
i = (2**64) - 1

Eine andere Evaluierungsreihenfolge kann man erzwingen durch ()

[79]:
i = 2 ** (64 - 1)
i
[79]:
9223372036854775808

… oder auch …

[80]:
i = (1)
i
[80]:
1

D.h. um () für ein einstelliges Tuple zu verwenden, schreibt man …

[81]:
t = (1,)
t
[81]:
(1,)

Besseres Laufzeitverhalten durch Geeignetere Datenstrukturen

Große Datenmengen in einer Liste abzulegen ist nicht gut: man sucht in einer solchen linear. Enter Dictionaries.

[82]:
d = {3:'drei', 1:'one', 4:'vier'}  # vorstellungsvermoegen: 3 milliarden elemente
[83]:
d[4]
[83]:
'vier'
[84]:
users = {'faschingbauer': 'joerg', 'kurz': 'sebastian'}
[85]:
gefundener_user = users['kurz']
gefundener_user
[85]:
'sebastian'

Errors

[86]:
try:
    gefundener_user = users['hugo']
except KeyError:
    print('nix gibt')    # was machma jetzt?
nix gibt

Will man keine Exception behandeln (vielleicht weil das Nichtvorhandenseins eines Elementes keine Ausnahme, sondern die Regel ist):

[87]:
gefundener_user = users.get('hugo')
if gefundener_user:
    print('jetzt machma was mim hugo')
else:
    users['hugo'] = 'victor'

Boolean: Short Circuit Evaluation

[88]:
def f():
    print('f')
    return True
def g():
    print('g')
    return False
[89]:
if f() and g():
    print('beides')
f
g

Aha: der ganze Ausdruck ist False, aber um das festzustellen, müssen beide Operanden evaluiert werden. D.h. beide Funktionen werden aufgerufen.

[90]:
if g() and f():
    print('beides')
g

Aha: wenn man weiss, dass der linke Operand bereits False ist, wird sich das Gesamtergebnis nicht mehr ändern.

Analog bei or

[91]:
if f() or g():
    print('jaja')
f
jaja

Aha: wenn der linke True ist, kann man sich den rechten sparen.

[92]:
if g() or f():
    print('jaja')
g
f
jaja

Aha: wenn der linke False ist, kann der rechte noch …

while Loops, und warum sie nicht Pythonic sind

Potscherte Berechnung der Summe 1..100 (100 inklusive)

[93]:
summe = 0
i = 0
while i<=100:
    summe += i
    i += 1
print(summe)
5050

Das geht besser, wenn man ein wenig mehr weiss …

``range()``

[94]:
range(10)
[94]:
range(0, 10)
[95]:
for i in range(10):
    print(i)
0
1
2
3
4
5
6
7
8
9

Der einzige Zweck von range() ist, zu iterieren

``sum()``

[96]:
sum([1,2,3])
[96]:
6
[97]:
sum((1,2,3))
[97]:
6

sum() iteriert und bildet die Summe. list und tuple sind iterables der primitiveren Sorte.

``sum()`` und ``range()``

``range()`` ist ein Iterable der intelligenteren Sorte

[98]:
sum(range(1,4))     # inklusive 1, exklusive 4 -> 1..3 wie oben
[98]:
6
[99]:
sum(range(1,101))
[99]:
5050

Problem gelöst!

Mehr über range(): Iterator Protocol (Folie 161)

[100]:
r = range(3)
print(r)
range(0, 3)
[101]:
it = iter(r)
it
[101]:
<range_iterator at 0x7fe9c43c3c90>
[102]:
next(it)
[102]:
0
[103]:
next(it)
[103]:
1
[104]:
next(it)  # das letzte
[104]:
2
[105]:
try:
    next(it)
except StopIteration:
    print('ENDE DER FOR SCHLEIFE')
ENDE DER FOR SCHLEIFE

Iterator Protocol: iter und next werden von for intern verwendet

[106]:
r = range(3)

# zu Beginn: iter(r)
for i in r:    # für jede Iteration: next(it)
    print(i)
0
1
2

Mutability, Shallow und Deep Copy

[107]:
l1 = [1,2,3]
l2 = l1

Beide Variablen referenzieren das selbe Objekt: ihr Objektidentität ist die gleiche.

[108]:
id(l1) == id(l2)
[108]:
True

is macht das gleiche, nur kürzer: vergleicht Objektidentitäten, nicht Objektinhalt.

[109]:
l1 is l2
[109]:
True

So würde man Objektinhalt vergleichen:

[110]:
l1 == l2
[110]:
True

Listen sind mutable: beide Variablen referenzieren das selbe Objekt. Wenn man es über eine Variable modifiziert, sieht man die Modifikation über die andere:

[111]:
l1.append(4)
print(l1)
[1, 2, 3, 4]
[112]:
l2
[112]:
[1, 2, 3, 4]

Shallow Copy mit dem Slice-Operator

[113]:
l1
[113]:
[1, 2, 3, 4]

Kopieren, um sich (vermeintlich, siehe unten) vor Modifiktionen zu schützen:

[114]:
l3 = l1[:]
l3
[114]:
[1, 2, 3, 4]

l3 ist eine Kopie von l1: hat eine andere Objektidentität

[115]:
l1 is l3
[115]:
False

Inhalt ist der gleiche (klar)

[116]:
l1 == l3
[116]:
True

Hier der gewünschte Effekt:

[117]:
l1.append(5)
l1
[117]:
[1, 2, 3, 4, 5]
[118]:
l3
[118]:
[1, 2, 3, 4]

Deep Copy (copy.deepcopy())

Problem: geschachtelte Datenstrukturen (hier eine Liste, die eine Liste enthält)

[119]:
l1 = [1, [100, 200, 300], 3]
len(l1)
[119]:
3
[120]:
l1[1]
[120]:
[100, 200, 300]

Shallow copy …

[121]:
l2 = l1[:]

Gleicher Inhalt …

[122]:
l1 == l2
[122]:
True

Verschiedene Objekte …

[123]:
l1 is l2
[123]:
False

Aber: Shallow Copy: das von Index 1 referenzierte Objekt ist von beiden aus gesehen das selbe …

[124]:
l1[1] == l2[1]
[124]:
True
[125]:
l1[1] is l2[1]
[125]:
True
[126]:
l1[1].append(400)
[127]:
l2
[127]:
[1, [100, 200, 300, 400], 3]
[128]:
l1[1] is l2[1]
[128]:
True

Lösung (wenn man das als Problem erachtet)

[129]:
import copy
l1 = [1, [100, 200, 300], 3]
l2 = copy.deepcopy(l1)
l2
[129]:
[1, [100, 200, 300], 3]
[130]:
l1 is l2
[130]:
False
[131]:
l1[1] is l2[1]
[131]:
False

Alternative Lösung: reiche einfach Tuples weiter, dann ersparst du dir Kopien

[132]:
l1 = [1, (100, 200, 300), 3]
l2 = l1[:]
try:
    l2[1].append(400)
except:
    print('ist eh alles gut, kann keiner ran')
ist eh alles gut, kann keiner ran

Funktionen

Die built-in max() Funktion

[133]:
max(1,2)
[133]:
2
[134]:
max(1,2,5,1,666)   # kann auch mit mehreren Parametern
[134]:
666
[135]:
max([1,2,3])   # mit Listen
[135]:
3
[136]:
max(range(10))  # mit beliebigen Iterables
[136]:
9

Wenn man unbedingt will, kann man sie auch selbst definieren. Hier mit genau zwei Parametern.

[137]:
def maximum(a,b):
    if a < b:
        return b
    else:
        return a

Typ: function

[138]:
type(maximum)
[138]:
function

Integers sind vergleichbar: unterstützen den ``<`` Operator

[139]:
maximum(1,2)
[139]:
2

Strings unterstützen ihn auch

[140]:
maximum('joerg', 'andreas')
[140]:
'joerg'

Äpfel können allerdings nicht mit Birnen verglichen werden (dieses “Feature” existiert hingegen in zumindest Javascript und PHP)

[141]:
try:
    maximum(1,'joerg')
except TypeError:
    print('parameter mit falschem typ uebergeben')
parameter mit falschem typ uebergeben
[142]:
try:
    maximum(42, '42')
except TypeError:
    print('vergleich zwischen aepfeln und birnen nicht erlaubt')

vergleich zwischen aepfeln und birnen nicht erlaubt

Funktionen sind “First Class Objects”

[143]:
type(maximum)
[143]:
function
[144]:
id(maximum)
[144]:
140641996137808
[145]:
m = maximum
id(m)
[145]:
140641996137808
[146]:
m(1,2)
[146]:
2

Gotcha: Mutable Default Parameters (Folie 90 ff.)

[147]:
def add_to_list(a, l=[]):
    l.append(a)
    return l
add_to_list.__defaults__
[147]:
([],)
[148]:
meine_liste = [1,2,3]
meine_liste = add_to_list(666, meine_liste)
meine_liste
[148]:
[1, 2, 3, 666]

Defaultwert von l wurde nicht benutzt:

[149]:
add_to_list.__defaults__
[149]:
([],)
[150]:
l1 = add_to_list(1)
l1
[150]:
[1]

Nun wurde der Defaultwert das erste mal benutzt

[151]:
add_to_list.__defaults__
[151]:
([1],)
[152]:
l2 = add_to_list(2)
l2
[152]:
[1, 2]

Mit allerhand Seiteneffekten. Bitte aufpassen: so einen Fehler sucht man ewig!

[153]:
add_to_list.__defaults__
[153]:
([1, 2],)

Globale und lokale Variablen (Folie 92)

Eine Zuweisung an eine noch nicht existierende Variable macht diese

[154]:
def f():
    x = 1
    return x
[155]:
f()
[155]:
1

By Default sind Variablen lokal. D.h. man muss nichts extra machen, um Unfälle zu vermeiden.

f()’s x hat keine Wechselwirkungen mit einer eventuellen globalen Variable:

[156]:
x = 666
f()
[156]:
1
[157]:
x
[157]:
666

Will man Wechselwirkungen, erzwingt man sie

[158]:
def g():
    global x
    x = 1
    return x
[159]:
g()
[159]:
1
[160]:
x
[160]:
1

Anders beim Lesen von Variablen. Wenn es keine lokale gibt, wird im globalen Scope gesucht (genauer gesagt, im nächstäußeren Scope)

[161]:
def h():
    return x
[162]:
h()
[162]:
1

Fehler, wenn die Variable nirgends existiert.

[163]:
def gibtsnetglobal():
    return gibtsnet
[164]:
try:
    gibtsnetglobal()
except NameError:
    print('fehler: variable "gibtsnet" nicht im globalen scope')
fehler: variable "gibtsnet" nicht im globalen scope

Exercise: uniq() (Folie 94, Punkt 2)

Aufgabenstellung Write a function uniq() that takes a sequence as input. It returns a list with duplicate elements removed, and where the contained elements appear in the same order that is present in the input sequence. The input sequence remains unmodified.

Der Kandidat hatte die Aufgabe mittels Google gelöst :-), was erstens unsportlich und zweitens unnachhaltig ist, und drittens mir die Gelegenheit gab, etwas mehr über Dictionaries, Sets, Iterables und Performance zu erzählen.

[165]:
def uniq(seq):
    d = dict.fromkeys(seq)
    return list(d.keys())
[166]:
uniq(dict.fromkeys([2,1,666,2,42,666]))
[166]:
[2, 1, 666, 42]

Funktioniert ja, aber was passiert hier eigentlich?

[167]:
d = {}
d[42] = 'xxx'
d[7] = 'kksvjhbsk'
d[666] = 'sgkysdudsvvc'
d
[167]:
{42: 'xxx', 7: 'kksvjhbsk', 666: 'sgkysdudsvvc'}
[168]:
d.keys()
[168]:
dict_keys([42, 7, 666])

Das ist schon wieder so ein Iterable, wie range(). Diesmal zum iterieren über die Keys eine Dictionary.

[169]:
for k in d.keys():
    print(k)
42
7
666
[170]:
dict.fromkeys([2,1,666,2,42,666])
[170]:
{2: None, 1: None, 666: None, 42: None}

dict.fromkeys() iteriert über seinen Input. Irgendein Iterable, z.B. range()

[171]:
dict.fromkeys(range(10))
[171]:
{0: None,
 1: None,
 2: None,
 3: None,
 4: None,
 5: None,
 6: None,
 7: None,
 8: None,
 9: None}

Ausflug: was gibts sonst noch aus der Kategorie? Z.B. die built-in Function enumerate()

[172]:
number_strings = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
print(number_strings[0])
print(number_strings[9])
zero
nine
[173]:
for elem in enumerate(number_strings):
    print(elem)
(0, 'zero')
(1, 'one')
(2, 'two')
(3, 'three')
(4, 'four')
(5, 'five')
(6, 'six')
(7, 'seven')
(8, 'eight')
(9, 'nine')

Schon wieder: der dict Constructor, wenn man ihm ein Iterable gibt, erwartet er (key, value) Paare als Elemente

[174]:
translations = dict(enumerate(number_strings))
translations
[174]:
{0: 'zero',
 1: 'one',
 2: 'two',
 3: 'three',
 4: 'four',
 5: 'five',
 6: 'six',
 7: 'seven',
 8: 'eight',
 9: 'nine'}

Hat man ein Iterable, will aber eine Liste, kann das der list Constructor

[175]:
list([1,2,3])
[175]:
[1, 2, 3]
[176]:
[1,2,3][:]
[176]:
[1, 2, 3]
[177]:
list(range(10))
[177]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[178]:
list('abc')
[178]:
['a', 'b', 'c']

Zurück zu der Lösung des Übungsbeispiels: return list(d.keys())

[179]:
translations
[179]:
{0: 'zero',
 1: 'one',
 2: 'two',
 3: 'three',
 4: 'four',
 5: 'five',
 6: 'six',
 7: 'seven',
 8: 'eight',
 9: 'nine'}

Iteration über ein Dictionary ist Iteration über dessen Keys

[180]:
for elem in translations:
    print(elem)
0
1
2
3
4
5
6
7
8
9
[181]:
for elem in translations.keys():
    print(elem)
0
1
2
3
4
5
6
7
8
9

Zum Schluss: will man die Keys des Dictionary (dict.fromkeys())

[182]:
list(translations)
[182]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Zusammengefasst: hier eine “schönere” Lösung

“Schöner” heisst: pythonic. Die Input-Sequenz wird nicht kopiert (zum Unterschied von dict.fromkeys()). Jedes Element wird mit yield dem Aufrufer (vermutlich einem for Loop) zur Verfügung gestellt, sobald es bekannt ist. Zum Unterschied von list(d.keys), wo wiederum kopiert und alloziert wird.

[183]:
def uniq(seq):
    seen = set()
    for elem in seq:
        if elem in seen:
            continue
        seen.add(elem)
        yield elem

Funktionen als Parameter

Funktionen sind First Class Objects. Wenn man also zum Beispiel einen Integer als Parameter übergeben kann, warum nicht auch eine Funktion?

Beispiel: die built-in map() Funktion

[184]:
it1 = range(10)
it2 = range(10,20)
def multiply(n1, n2):
    return n1*n2
[185]:
combined_iterable = map(multiply, it1, it2)
[186]:
for i in combined_iterable:
    print(i)
0
11
24
39
56
75
96
119
144
171

List Comprensions (Folie 125)

map() ist meistens schlecht lesbar. Vor allem bei simplen Transformationen bieten sich List Comprehensions an.

Beispiel: generieren von Quadratzahlen

[187]:
# kompliziert

original = [0,1,2,3,4,5,6,7,8,9]
quadrate = []
for i in original:
    quadrat = i**2
    quadrate.append(quadrat)
quadrate
[187]:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Erste naeherung zum Pythonism: yield. Noch immer schwer lesbar, dafür performanter

[188]:
def gen_squares(it):
    for i in it:
        yield i**2
for num in gen_squares(original):
    print(num)
0
1
4
9
16
25
36
49
64
81

Wenn man unbedingt eine Liste will …

[189]:
list(gen_squares(original))
[189]:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Sehr lesbar: List Comprehension

[190]:
squares = [i**2 for i in original]
squares
[190]:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Generator Expressions: Lesbarkeit und Performance

Hier wird keine fette Liste alloziert, sondern (durch minimale syntaktische Änderung) ein Generator generiert

[191]:
squares = (i**2 for i in original)
squares
[191]:
<generator object <genexpr> at 0x7fe9c43f2900>
[192]:
for i in squares:
    print(i)
0
1
4
9
16
25
36
49
64
81

Code Review

pprint() ist praktisch für Debug-Output

[193]:
d = {
    'item-a': { 'one': 1, 'two': 2},
    'item-b': [1,2,3,4,5,6],
    'item-c': 'string',
}

[194]:
print(d)    # alles in einer Zeile
{'item-a': {'one': 1, 'two': 2}, 'item-b': [1, 2, 3, 4, 5, 6], 'item-c': 'string'}
[195]:
import pprint
pprint.pprint(d, indent=4)
{   'item-a': {'one': 1, 'two': 2},
    'item-b': [1, 2, 3, 4, 5, 6],
    'item-c': 'string'}

Straightforward Datentransformation

[196]:
userliste = [
    {
        'Name': 'Joerg Faschingbauer',
        'blah': 'nochwas',
    },
    {
        'Name': 'Victor Hugo',
        'blah': 'was auch immer da noch steht',
    },
    {
        'Name': 'Sebastian Kurz',
        'blah': 'blah',
    }
]

Nicht pythonic: Index-based Iteration zum generieren der Dropdown-Liste

[197]:
dropdownliste = []
for i in range(len(userliste)):
    name = userliste[i]['Name']
    dropdownliste.append(name)
dropdownliste
[197]:
['Joerg Faschingbauer', 'Victor Hugo', 'Sebastian Kurz']

List Comprehension: kürzer und weniger fehleranfällig (weil kürzer und trotzdem lesbar: pythonic)

[198]:
dropdownliste = [user['Name'] for user in userliste]
dropdownliste
[198]:
['Joerg Faschingbauer', 'Victor Hugo', 'Sebastian Kurz']

Ausflug: More on Dictionaries (Folie 127ff.)

[199]:
d = dict(enumerate(['zero', 'one', 'two', 'three']))
d
[199]:
{0: 'zero', 1: 'one', 2: 'two', 3: 'three'}

Keys sind iterable

[200]:
for k in d.keys():
    print(k)
0
1
2
3

Oder so …

[201]:
for k in d:
    print(k)
0
1
2
3
[202]:
list(d.keys())
[202]:
[0, 1, 2, 3]

Value sind iterable

[203]:
for v in d.values():
    print(v)
zero
one
two
three

Key/Value Paare iterieren? Wie geht das denn nun?

[204]:
for item in d.items():
    print(item)
(0, 'zero')
(1, 'one')
(2, 'two')
(3, 'three')
[205]:
for item in d.items():
    key = item[0]
    value = item[1]
    print('key', key)
    print('value', value)
key 0
value zero
key 1
value one
key 2
value two
key 3
value three

There must be a better way: Tuple Unpacking

[206]:
a, b = 1, 2
[207]:
(a, b) = (1, 2)

Tuple Unpacking bei Key/Value Iteration

[208]:
for key, value in d.items():
    print('key', key)
    print('value', value)
key 0
value zero
key 1
value one
key 2
value two
key 3
value three

Now for something completely different: collections.namedtuple

[209]:
user_a = {
    'firstname': 'joerg',
    'lastname': 'faschingbauer',
}

user_b = {
    'firstname': 'sebastian',
    'lastname': 'kurz',
}

users = [user_a, user_b]
for u in users:
    print('First Name', u['firstname'])
    print('Last Name', u['lastname'])
First Name joerg
Last Name faschingbauer
First Name sebastian
Last Name kurz

There must be a better way

Erste Näherung: Definition einer Klasse, damit man nicht immer mit den nackten Dictionary-Keys arbeiten muss

[210]:
class User:
    def __init__(self, firstname, lastname):
        self.firstname = firstname
        self.lastname = lastname

user_a = User(firstname='joerg', lastname='faschingbauer')
user_b = User('sebastian', 'kurz')
users = [user_a, user_b]
for u in users:
    print('First Name', u.firstname)
    print('Last Name', u.lastname)
First Name joerg
Last Name faschingbauer
First Name sebastian
Last Name kurz

There must be a better way. Oft, wenn man mit Daten hantiert, definiert man viele solcher Klassen mit immer anderem Inhalt. Dafür gibts ``collections.namedtuple``.

[211]:
from collections import namedtuple

User = namedtuple('User', ('firstname', 'lastname'))
user_a = User(firstname='joerg', lastname='faschingbauer')
user_b = User('sebastian', 'kurz')
users = [user_a, user_b]
for u in users:
    print('First Name', u.firstname)
    print('Last Name', u.lastname)
First Name joerg
Last Name faschingbauer
First Name sebastian
Last Name kurz

Mehr zur Parameterübergabe: variabel lange Argumentlisten

An einer Stelle im reviewten Code wurde * bei einem Funktionsaufruf verwendet. Das kann ich nicht so stehen lassen, sonder muss der Stackoverflowprogrammierung entgegenwirken. Und ein wenig weiter ausholen.

[212]:
def velocity(length_m, time_s):
    return length_m/time_s
[213]:
velocity(12, 2)
[213]:
6.0

Keyword Arguments (Folie 91): lesbarer, wenn mehr als ein Parameter im Spiel ist.

[214]:
velocity(time_s=2, length_m=12)
[214]:
6.0

Parameter liegen in einer liste vor, und werden als positionelle Parameter übergeben:

[215]:
params = [12, 2]
velocity(*params)
[215]:
6.0

Ist das gleiche wie …

[216]:
velocity(12, 2)
[216]:
6.0

Parameter liegen als Dictionary vor, und werden als Keyword Arguments übergeben:

[217]:
params = {'length_m': 12, 'time_s': 2}
velocity(**params)
[217]:
6.0

Ist das gleiche wie …

[218]:
velocity(time_s=2, length_m=12)
[218]:
6.0

Wie sieht es auf der anderen Seite aus? In der Funktion?

[219]:
def velocity(*args):
    m, s = args    # explizites Entpacken der Positionellen Parameter
    return m/s
[220]:
velocity(12, 2)
[220]:
6.0
[221]:
def velocity(**kwargs):
    # explizies Rauskletzeln der Keyword Arguments
    m = kwargs['length_m']
    s = kwargs['time_s']
    return m/s
[222]:
velocity(length_m=12, time_s=2)
[222]:
6.0

OO: eine erste Klasse

[223]:
class User:
    def __init__(self, first, last, age):
        self.first = first
        self.last = last
        self.age = age
    def celebrate_another_birthday(self):
        self.age += 1
    def greet(self):
        print(f'Guten Tag, mein Name ist {self.first} {self.last}, und ich bin {self.age} Jahre alt')
[224]:
user = User('joerg', 'faschingbauer', 53)
print(user.first, user.last, user.age)
joerg faschingbauer 53
[225]:
user.celebrate_another_birthday()
[226]:
user.age
[226]:
54
[227]:
print(type(user))
<class '__main__.User'>
[228]:
user.greet()
Guten Tag, mein Name ist joerg faschingbauer, und ich bin 54 Jahre alt