Seite 1 von 2
List Comprehension richtig verstehen
Verfasst: Mittwoch 28. Oktober 2015, 23:09
von Sophus
Hallo Leute,
ich setze mich wieder nach längerer Zeit an Python ran, und mir fiel gleich auf, dass ich die Listen-Abstraktion (List Comprehension) nicht richtig verstehe. Dazu habe ich mir mal ein Beispiel mitgebracht. Beim Anblick dieses Quelltextes habe ich versucht diesen Quelltext gedanklich zu verstehen. Mein Erklärungsversuch seht ihr unten.
Code: Alles auswählen
import re
def password_check(password_prompt):
password_scores = {0:'Horrible', 1:'Weak', 2:'Medium', 3:'Strong', 4:'More Strong', 5:'Very Strong'}
print "password_scores", password_scores
password_strength = dict.fromkeys(['has_upper', 'has_lower', 'has_num', 'has_digit', 'has_symbolic'], False)
print "password_strength",password_strength
if re.search(r'[A-Z]', password_prompt):
has_upper = password_strength['has_upper ='] = True
print "has_upper =", has_upper
if re.search(r'[a-z]', password_prompt):
has_lower = password_strength['has_lower'] = True
print "has_lower =", has_lower
if re.search(r'[0-9]', password_prompt):
has_num = password_strength['has_num'] = True
print "has_num =", has_num
if re.search(r"\d", password_prompt):
has_digit = password_strength['has_digit'] = True
print "has_digit =", has_digit
if re.search(r"[ !#$%&'()*+,-./[\\\]^_`{|}~"+r'"]', password_prompt):
has_symbolic = password_strength['has_symbolic'] = True
print "has_symbolic =", has_symbolic
score = len([b for b in password_strength.values() if b])
print "Score", score
print ('Password is %s' % password_scores[score])
return (password_scores[score])
password = raw_input('Password: ... ')
pwd_chk = password_check(password)
print pwd_chk
Ich konzentriere mich auf Zeile
24.
Soweit ich weiß, besteht eine List Comprehension grundlegend aus einem Ausdruck, gefolgt von beliebig vielen for/in-Bereichen und von eventuellen Bedingungen. Die Bedingungen sind aber optional. Allgemein weiß ich, dass sich der for/in-Bereich einer List Comprehension der for-Schleife syntaktisch ähnelt. Soweit die Theorie.
Nun versuche ich folgenden Ausdruck in Worten zu fassen:
Code: Alles auswählen
score = len([b for b in password_strength.values() if b])
Beginne die List Comprehension mit einem Ausdruck
b. Iteriere anschließend im for/in-Bereich jedes Element
b über die Dictionarie
password_strength. Anschließend erzeuge mit der Methode
values() aus diesem Dictionarie eine Liste, die nur aus den Werten - ohne Schlüssel - besteht, aber nur sofern die Bedingung
b True ergibt, und fügte das Ergebnis in die neue Liste. Die List Comprehension ist hier zum Inhalt der Methode
len(). Berechne die Länge vom neuen Ergebnis und speichere diese in das Attribut
score.
Habe ich das richtig formuliert oder etwas übersehen oder gar falsch formuliert?
Re: List Comprehension richtig verstehen
Verfasst: Mittwoch 28. Oktober 2015, 23:12
von /me
Sophus hat geschrieben:Code: Alles auswählen
score = len([b for b in password_strength.values() if b])
Bei deinem Beispielcode wird über
password_strength.values() iteriert.
Ohne list comprehension könnte das so aussehen.
Code: Alles auswählen
data = []
for b in password_strength.values():
if b:
data.append(b)
score = len(data)
Re: List Comprehension richtig verstehen
Verfasst: Mittwoch 28. Oktober 2015, 23:18
von cofi
Jede LC kann man herunterbrechen:
~
Code: Alles auswählen
result = []
for x in xs:
if condition(x):
result.append(dostuff(x))
Insofern ist "Abstraktion" fuer eine LC IMO etwas zu viel, das ist nicht mehr als syntaktischer Zucker.
Abgesehen davon laesst sich die LC in deinem Beispiel als
schreiben
Re: List Comprehension richtig verstehen
Verfasst: Mittwoch 28. Oktober 2015, 23:30
von Sophus
Danke euch. Also, die For-Schleife verstehe ich problemlos, aber bei LC fange ich immer wieder an von vorne zu denken. Rein von der Geschwindigkeit macht es keinen Unterschied, ob ich über die LC oder For-Schleife laufen lasse? Wobei cofi in meinem Fall aufgezeigt hat, dass man sowohl auf die For-Schleife als auch auf LC verzichten kann.
Re: List Comprehension richtig verstehen
Verfasst: Mittwoch 28. Oktober 2015, 23:34
von Sirius3
@Sophus: in diesem Fall würde man erst gar keine Liste erzeugen, sondern entweder einen Generatorausdruck verwenden:
und dann sieht man gleich, dass gar kein for nötig ist:
Ohne den Umweg über ein Wörterbuch macht das for dann wieder Sinn:
Code: Alles auswählen
PASSWORD_SCORES = ['Horrible', 'Weak', 'Medium', 'Strong', 'Very Strong']
PASSWORD_STRENGTHS = ['[A-Z]', '[a-z]', '[0-9]', r'''[ !#$%&'()*+,-./[\\\]^_`{|}~"]''']
def password_check(password_prompt):
score = sum(bool(re.search(exp, password_prompt)) for exp in PASSWORD_STRENGTHS)
return PASSWORD_SCORES[score]
Re: List Comprehension richtig verstehen
Verfasst: Mittwoch 28. Oktober 2015, 23:43
von Sophus
Puh, so viele Informationen auf einmal. Das muss ich erst einmal verdauen, Sirius3.
Aber eine Frage habe ich noch.
Sowohl cofi als auch du (Sirius3) bringt als Beispiel:
an.
Zunächst habe ich aus reiner Neugier folgendes gemacht:
Code: Alles auswählen
score = (password_strength.values())
print "Score", score
Nachdem ich ein Passwort eingegeben habe, wurde mir folgendes über die print-Anweisung ausgegeben:
Score [True, False, True, False, False, False]
Und bei der
sum()-Methode müsste das Ergebnis ja 5 sein (schließlich beginnt Python bei der Zählung immer mit 0).
Verwende ich aber eure Vorschläge:
Code: Alles auswählen
score = sum(password_strength.values())
print "Score", score
Wird mir über die Print-Anweisung folgendes ausgegeben:
Score 2
Werden hier nur die
True-Elemente gezählt?
Re: List Comprehension richtig verstehen
Verfasst: Mittwoch 28. Oktober 2015, 23:47
von /me
Sophus hat geschrieben:Werden hier nur die True-Elemente gezählt?
Gezählt wird gar nichts sondern aufsummiert.
Re: List Comprehension richtig verstehen
Verfasst: Mittwoch 28. Oktober 2015, 23:48
von cofi
Wenn du die `for` Schleife verstehst, dann ist die LC nur ein kleiner Schritt davon weg: Es gibt eine direkte Entsprechung.
Die Ausfuehrungsgeschwindigkeit kann man vernachlaessigen, aber die _Lese_geschwindigkeit ist bei einer LC definitiv hoeher (wenn man sie gewohnt ist), da der Ausdruck direkt widerspiegelt was der Autor im Sinn hatte, waehrend die `for`-Schleife das auf verschiedene, kleine Schritte verteilt, die u.U. auch noch viel weiter verteilt sind als in dem Beispiel.
Wuerde der `sum` Ausdruck 5 ergeben, wuerde unser Vorschlag zum Ersatz keinen SInn machen

Warum es funktioniert:
Code: Alles auswählen
In [1]: int(True)
Out[1]: 1
In [2]: int(False)
Out[2]: 0
Re: List Comprehension richtig verstehen
Verfasst: Mittwoch 28. Oktober 2015, 23:50
von pillmuncher
@Sophus:
sum() zählt nicht, sondern summiert, und jedes
bool ist ein
int:
Code: Alles auswählen
In [1]: sum([3, 5, 9])
Out[1]: 17
In [2]: isinstance(True, int)
Out[2]: True
In [3]: int(True)
Out[3]: 1
In [4]: int(False)
Out[4]: 0
Re: List Comprehension richtig verstehen
Verfasst: Mittwoch 28. Oktober 2015, 23:56
von Sophus
Ich hatte das irgendwie falsch verstanden. Ich dachte eher so:
Bei diesem Ausdruck
Code: Alles auswählen
score = (password_strength.values())
print "Score", score
sehe ich
Score [True, False, True, False, False, False]
fünf Elemente (gezählt mit 0).
Und mit der Methode sum() soll dann die Summe aller Elemente einer Liste zusammen
gezählt werden.
So habe ich das gelesen

Re: List Comprehension richtig verstehen
Verfasst: Donnerstag 29. Oktober 2015, 00:37
von pillmuncher
@Sophus: Du verwechselst
Kardinalzahlen und
Ordinalzahlen.
Außerdem: In
len(x) ist
x nicht der
Inhalt der Funktion
len(), sondern ein sog.
Argument.
List Comprehensions kommen aus Haskell, wurden aber der Mengenkomprehension von Gottlob Frege, einem der zwei Erfinder der modernen Prädikatenlogik, nachempfunden. Den Ausdruck List Comprehension hat Philip Wadler erfunden.
List Comprehensions sind Anwendungen von Applikativen Funktoren. Ein Funktor ist etwas, worüber man ein Mapping ausführen kann, so wie das die Python-Funktion
map() macht:
Code: Alles auswählen
In [5]: list(map(lambda x: x * 2, [1, 2, 3, 4, 5]))
Out[5]: [2, 4, 6, 8, 10]
Hier ist die Liste [1, 2, 3, 4, 5] der Funktor, da diese List dasjenige ist, worüber das Mapping ausgeführt wird. Dazu gibt es eine äquivalente List Comprehension:
Code: Alles auswählen
In [6]: [(lambda x: x * 2)(y) for y in [1, 2, 3, 4, 5]]
Out[6]: [2, 4, 6, 8, 10]
Oder kürzer:
Code: Alles auswählen
In [7]: [y * 2 for y in [1, 2, 3, 4, 5]]
Out[7]: [2, 4, 6, 8, 10]
Wenn man dagegen mehrere Funktoren hat (zB. zwei Listen), über die man gleichzeitig ein Mapping ausführen will, funktioniert
map() nicht mehr. Eine List Comprehension dagegen schon:
Code: Alles auswählen
In [8]: [x + y for x in [1, 2, 3, 4, 5] for y in [6, 7, 8]]
Out[8]: [7, 8, 9, 8, 9, 10, 9, 10, 11, 10, 11, 12, 11, 12, 13]
In Haskell gibt es neben der List Comprehension eine Funktion namens <*>, die das ohne List Comprehension ermöglicht. Ein Funktor, auf dem diese Funktion definiert ist, ist ein sog. Applikativer Funktor. In Python gibt es kein solches Konstrukt, aber dafür die List Comprehension, die dazu äquivalent ist.
Re: List Comprehension richtig verstehen
Verfasst: Donnerstag 29. Oktober 2015, 02:56
von Sophus
Besten Dank, pillmuncher. Ich werde das alles verdauen müssen. Wobei ich zugeben muss, dass ich mich mit Haskell gar nicht auskenne.
Re: List Comprehension richtig verstehen
Verfasst: Donnerstag 29. Oktober 2015, 09:59
von kbr
Man muß sich mit Haskell nicht auskennen um List Comprehensions (LCs) zu verstehen, aber es ist sicher schön zu wissen, dass dieses Konstrukt in Python aus Haskell entlehnt ist. In Ergänzung zu Cofis Anmerkung haben LCs Geschwindigkeitsvorteile gegenüber dem Aufbau einer Liste per for-Loop und append. Sie sind auch schneller als entsprechende Generator-Expressions. In der Praxis machen sich diese Unterschiede aber erst bei häufiger Iteration über diese Konstrukte bemerkbar. Bei großen Listen sind dann wieder Generatoren von Vorteil. Wann die jeweiligen Grenzen erreicht sind, lässt sich im Bedarfsfall durch Profiling ermitteln. Bei Listen mit 5 Elementen (und auch etwas größere), die zudem nur gelegentlich erzeugt werden, machen sich diese Unterschiede nicht bemerkbar. Aber wenn man LCs einmal verstanden hat, sind sie leicht zu lesen, schnell zu schreiben und man hat zudem das gute Gefühl auch flotten Code erstellt zu haben ...

Re: List Comprehension richtig verstehen
Verfasst: Donnerstag 29. Oktober 2015, 12:04
von Sophus
Vielen Dank, kbr. Im Grunde möchte ich als Anfänger alles verstehen. Ich möchte jeden kleinen Schritt verstehen. Aber eine kleine, und vielleicht peinliche, Frage habe ich noch. Wenn ich mir folgende LC anschaue:
Verstehe ich den For/In-Bereich (
for b in list1) und den If-Statement (
if b). Und ich weiß auch, dass das erste
b ein Ausdruck ist, mit dem die LC immer beginnt. Was macht das erste
b in diesem Fall? Es ist schon mal nicht im For/In-Bereich, also iteriert wird dieser Ausdruck nicht, und in der Bedingung ist er auch nicht mit eingebunden. Und ich glaube auch nicht, dass das erste
b nur als Dekoration dient.
Re: List Comprehension richtig verstehen
Verfasst: Donnerstag 29. Oktober 2015, 12:44
von pillmuncher
@Sophus: Wenn man deine List Comprehension auf Deutsch reformuloeren würde, käme ungefähr sowa raus:
Die Liste derjenigen
bs, so dass
b ein Element aus
list1 ist und
bool(b) wahr ist. Die Reihenfolge bleibt erhalten.
Die Teile einer einfachen List Comprehension sind: Ein Ausdruck, der eine Funktion (im mathematischen Sinne, nicht im Python-Sinne) darstellt, dessen formale Parameter über den nachfolgenden Iterationsausdruck gebunden wird, und optional folgend ein Prädikat (ebenfalls im mathematischen Sinne), dessen formale Parameter ebenfalls durch den vorhergehenden Iterationsausdruck gebunden werden und das dazu dient, die Folge der Elemente, auf denen der erste Ausdruck operiert, zu beschränken/filtern. Iterations- und Prädikatausdrücke können wiederholt werden. LCs haben also diese Struktur:
x,
xs,
fx und
px sind in diesem Kontext nicht als Python-Namen zu verstehen, sondern als
metasyntaktische Variablen und Formeln. Auch " ist hier ein metasyntaktisches Symbol.
fx ist irgendein Ausdruck der auf
x basiert, ebenso
px. Ersteres liefert das, was Element der neuen Liste werden soll, und letzteres filtert die Ausgangselemente.
? bedeutet: das in eckigen Klammern stehende vorausgehende Konstrukt kann 0 oder 1 mal vorkommen.
+ bedeutet: das in eckigen Klammern stehende vorausgehende Konstrukt kann 1 mal oder öfter vorkommen.
Re: List Comprehension richtig verstehen
Verfasst: Donnerstag 29. Oktober 2015, 12:46
von stefanbunde
die LC gibt dir ja eine liste mit elementen zurück.
und der erste wert dort gibt an, wie die elemente aussehen sollen.
in deinem fall wird einfach nur b zurück gegeben
dieses beispiel würde eine liste mit den quadratzahlen von b zurückgeben, sofern b weder 0 noch None oder False ist
Re: List Comprehension richtig verstehen
Verfasst: Donnerstag 29. Oktober 2015, 12:55
von Sophus
Hallo pillmuncher und Stefanbunde,
wenn ich es also richtig verstehe, dann dient der Ausdruck (b) für die neue Liste, die vom LC erzeugt wurde? Das heißt, all die neuen Elemente werden über diesen Ausdruck (b) in die neue Liste hinzugefügt? Im Umkehrschluss: Würde ich rein hypothetisch diesen Ausdruck weglassen, hätte die LC keine Möglichkeit eine neue Liste mit neuen Elementen zu erstellen, da der Ausdruck fehlt?
Re: List Comprehension richtig verstehen
Verfasst: Donnerstag 29. Oktober 2015, 13:13
von kbr
@Sophus: Um es umgangssprachlich auszudrücken: das erste 'b' in der LC ist das, "was in die Liste kommt" - das, was sonst in einer for-Loop per append hinzugefügt würde.
Re: List Comprehension richtig verstehen
Verfasst: Donnerstag 29. Oktober 2015, 13:15
von Sophus
Danke dir, kbr. Jetzt geht ein Licht bei mir auf.
Re: List Comprehension richtig verstehen
Verfasst: Donnerstag 29. Oktober 2015, 13:59
von snafu
Oder nochmal anders geschrieben:
[
Tätigkeit-mit-Element for
Element in
Quell-Liste]
Und alternativ mit einer Bedingung als Zusatz:
[
Tätigkeit-mit-Element for
Element in
Quell-Liste if
Bedingung-für-Element]
Bei der zweiten Form landen nur die Elemente im Ergebnis, für welche die Bedingung zutrifft. Das ist also ein Filter, um das Ergebnis einzuschränken.
Tätigkeit-mit-Element kann auch das Element selbst sein, ohne dass damit eine Operation ausgeführt wird. Das kann sinnvoll sein, wnen man ein
if im Ausdruck hat. Also in dieser Art:
EDIT: Und mit "Quell-Liste" ist allgemein ein iterierbares Objekt gemeint, wie z.B. auch ein Tupel oder ein String. Ich wollte es sprachlich hier etwas einfacher halten, weil der Fragesteller da meiner Meinung nach mehr von hat.