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