Bernhard Häussner

Über Pythons kurze Listennotation

03.07.2011, 20:34
Python-Code mit Listennotation

Python-Code mit Listennotation

Als ich mir Python (3.2) anschaute, musste ich mich zunächst an das Fehlen traditioneller for-Schleifen gewöhnen, doch inzwischen finde ich großen Gefallen an den Iterationsmethoden von Python und an der Listennotation (List Comprehensions).

Zunächst ein einfaches Programm mir dieser Listenverarbeitungssyntax. Folgendes Python-Stück erstellt eine Liste mit den ersten 5 Quadratzahlen von ungeraden Zahlen:

>>> [i**2 for i in range(10) if i%2==1]
[1, 9, 25, 49, 81]

Jetzt ein Beispiel aus der Python-Dokumentation, wie man zwei Vektoren multiplizieren kann:

>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
>>> [vec1[i]*vec2[i] for i in range(len(vec1))]
[8, 12, -54]

Wobei ich anhand dieses Codefetzens zeigen will, dass sich Dinge auf vielerlei Arten und mitunter einfacher machen lassen. Man kann unter Verwendung von zip() über zwei Listen gleichzeitig iterieren: (mit enumerate() ließe sich auch noch über den Index iterieren)

>>> [e1*e2 for e1,e2 in zip(vec1,vec2)]
[8, 12, -54]

An den nächsten Codezeilen sehen wir erstmals, wie schön in Python ähnliche Datentypen die gleichen Operatoren verwenden: Der Intervall-Itarator, welcher von range() zurückgegeben wird, kann als Sequenztyp wie eine Liste zerstückelt werden:

>>> a=5
>>> [i for i in range(200-a*2,200,2)]
[190, 192, 194, 196, 198]
>>> [i*2 for i in range(100)[-a:]]
[190, 192, 194, 196, 198]

Sehr praktisch sind auch die Aggeregatfunktionen all(), any(), max(), min(), sum(), len() da sich mit ihnen viel Boilerplate-Code vermeiden lässt, der so ähnlich aussieht wie dieser:

>>> testlist=[1,8,9]
>>> test=False
>>> for x in testlist:
...     if(x>4):
...         test=True
...         break
...
>>> test
True

Nach dramatischer Verkürzung:

>>> any(x>1 for x in testlist)
True

Interessanterweise bleibt der Effekt von break erhalten. Das kann man prüfen, indem man mit yield-Syntax ein eigenes, iterierbares Objekt erstellt, welches beim iteriert-werden Statusmeldungen ausgibt. Hier wird die 9 nicht mehr getestet:

>>> def worker(l):
...     for i in l:
...         print("Proccessing %02d" % i)
...         yield i>4
...
>>> w2=worker(testlist)
>>> any(w2)
Proccessing 01
Proccessing 08
True

PS: In Haskell (von welchem Python seine Listennotation hat) kann man den selben Effekt noch schöner an unendlichen Listen sehen: any (> 5) [0..] ergibt True.

Man kann die Aggregatfunktionen auch auf Strings anwenden, da auch diese iterierbar sind:

>>> any(""),any("ha")
(False, True)
>>> max("Panzer")
'z'

Für eine Suchfunktion (theoretische Informatik...) brauchte ich eine Methode herauszufinden, wie lange das längste Präfix eines Strings ist, welches ein Suffix eben jenes Strings und eines weiteren Buchstaben ist. Was ich zunächst hier mit traditionellen Methoden als 10-zeilige Funktion geschrieben, habe ich dann hier als eine Listenverarbeitung schreiben können:

>>> v="abcabd"
>>> i=5
>>> b="c"
>>> max([0]+[k for k in range(0,i+2) if (v[:i]+b)[-k:]==v[:k]])
3

Oder ich habe gemessen, dass die Diagonale in einem Rechteck 29,4° geneigt ist, aber wie war das Seitenverhältnis? Da sorted() eine lexikographische Ordnung herstellt, kann man mehrere Tupel sortieren, wobei der erste Eintrag das Sortierkriterium ist und die weiteren die Ausgangsdaten. Dann baut man noch eine zweite Listennotaton außen herum zum Formatieren. Hiermit findet man also heraus, dass es wohl 16:9 war:

>>> import math,fractions
>>> ", ".join(["{}:{}".format(b,a) for diff,a,b in sorted(
...     [(abs(math.atan(a/b)-((29.4/360)*2*math.pi)),a,b)
...         for a in range(20) for b in range(20)
...       if b>a>0 and fractions.gcd(a,b)==1 ]
... )[:3]])
'16:9, 7:4, 9:5'

Hier sieht man schon ein Problem der Listennotation: Sie wird schnell unübersichtlich, da dutzende Befehle in einem Ausdruck stehen, und man auch eher von hinten nach vorne lesen muss.

Da man mit dem +-Operator Listen konkatenieren kann, kann auch die sum()-Funktion beliebig viele Listen aus einer Liste konkatenieren. Hiermit werden je 3 Zahlen startend bei 0,9 und 18 in eine Liste geschrieben:

>>> sum([range(i,i+3) for i in range(0,27,9)],[])
[0, 1, 2, 9, 10, 11, 18, 19, 20]

Das einzige, was man hier tun muss, ist sum() eine leere Liste als zweites Argument zu übergeben, da sonst die Listen zum Standartanfangswert 0 addiert werden würden (Exception). Etwas komisch ist dann leider das:

>>> sum((str(i) for i in [1,2,345,6,78]),"")
Traceback (most recent call last):
  File "", line 1, in 
TypeError: sum() can't sum strings [use ''.join(seq) instead]

Und wenn man denkt, man weiß alles über Listen, fängt man mit Mengen an:

>>> noprimes={j for i in range(2, 8) for j in range(i*2, 50, i)}
>>> set(range(2, 50))-noprimes # Primzahlen!
{2, 3, 5, 7, 41, 11, 13, 47, 17, 37, 19, 43, 23, 29, 31}
>>> {chr(i) for i in range(97,97+26)} - set( # Pangram powers!
... "The quick brown fox jumps over the lazy dog" )
set() 

Wie man sieht bilden die Iterator- und Sequenztypen ein mächtiges Instrument, wenn man Python programmiert. Wie vieles kann man sie aber auch missbrauchen.

Kurze URL http://1-co.de/b/1q. Post to twitter

Kommentare

keine





 
Χρόνογραφ
© 2008-2017 by Bernhard Häussner - Impressum - Login
Kurz-Link zu dieser Seite: http://1-co.de/b/1q