Python Listenverständniss

Wenn du lernst, mit Python zu programmieren, wirst du immer wieder auf Listen stoßen. Listen sind eine der vielseitigsten und grundlegendsten Datenstrukturen in Python. Dabei werden Werte zwischen eckige Klammern gesetzt - [ ] und durch Kommas getrennt. Folgend ein Beispiel: 

planets = ['mars', 'jupiter', 'earth', 'pluto', 'uranus', 'saturn', 'mercury']

An sich sind Listen recht einfach zu verstehen und zu manipulieren. Was vielleicht etwas verwirrend sein kann, ist es, sie auch zu verstehen. Das Listenverständnis ist eine ‘pythonische’ Methode, mit der man kürzere, prägnante Listen erstellen kann. Sie funktionieren genau wie herkömmliche Listen und können für Iterationen und für kurze bedingte Ausdrücke verwendet werden. Sie sind ein guter Ersatz für map()-, reduce()- und filter()-Funktionen.

In diesem Artikel werden wir uns mit dem Verständnis von Listen im Gegensatz zu den eigentlichen Listen beschäftigen. Wir werden uns ansehen, wann wir sie verwenden sollten und Beispiele dafür geben, wie wir sie verwenden können.

Syntax

Die Syntax “for” ist das erste, was deine Aufmerksamkeit auf das Listenverständnis lenkt. Es scheint gegen die normale Reihenfolge beim Schreiben von Python-Code zu verstoßen, da der Ausdruck vor der Schleife steht. Die Art und Weise, wie das Listenverständnis geschrieben wird, gibt ihm den schnelleren Vorteil gegenüber der Liste. Hier allokiert Python zuerst den Speicher der Liste, anstatt die Liste zur Laufzeit in der Größe zu verändern.

Von der Syntax her hat jedes Listenverständnis eine for-Schleife. Sie sind im Grunde genommen iterables, die eine weitere Liste erzeugen.

Listenverständnis und die for-Schleife

Listenverständnis bietet eine übersichtliche Möglichkeit, Listen zu erstellen. Gängige Anwendungsmöglichkeiten sind die Erstellung neuer Listen, in denen jedes Element das Ergebnis einiger Operationen ist, die auf jedes Mitglied einer anderen Sequenz angewendet werden oder iterierbar sind. Man kann ebenso eine Untersequenz der Elemente erstellen, die eine bestimmte Bedingung erfüllen.

Python Docs for Python 3

Obwohl jedes Listenverständnis als for-Schleife geschrieben werden kann, kann nicht jede for-Schleife als Listenverständnis umgeschrieben werden. Dies ist eine einschränkende Bedingung für das Listenverständnis.

Folgend haben wir ein Beispiel, das den Unterschied in der Verwendung von for- in Listen und Listenverständnissen klar machen sollte:

names = []
  for name in "Beyonce Knowles":
    names.append(name)
    print(names) 
#['B', 'e', 'y', 'o', 'n', 'c', 'e', ' ', 'K', 'n', 'o', 'w', 'l', 'e', 's']

Der obige Code ist ein typisches Beispiel dafür, wie for-Schleifen zur Iteration von Strings verwendet werden. Es kann mit Hilfe von Listenverständnissen wie diesem umgeschrieben werden:

her_name = [names for names in 'Beyonce Knowles']
print(her_name) 

Listenverständnis mit eingebauten Funktionen

Listenverständnis kann auch für mathematische Berechnungen und andere Iterationen verwendet werden. Es funktioniert perfekt mit Funktionen wie range(), enumerate(), zip() und len().

Hier ist ein Codeausschnitt, mit dem man alle geraden Zahlen in einem Bereich von 0 bis 10 erhält.

add = [] 
  for x in range(0, 10, 2): 
    add.append(x) 
...
print(add)
# [2, 4, 6, 8]

Im Listenverständnis umgeschrieben, sieht es folgendermaßen aus:

add = [x for x in range(0,10,2)] 
print(add)
# [2, 4, 6, 8]

Die len()-Funktion ist in der Regel nicht iterabel. Indem wir sie in ein range() einschließen, können wir die Länge einer gegebenen Zeichenkette, eines Tupels oder einer Liste in einem Ausdruck erhalten. Die Ausgabe ist jedoch keine einzelne Ziffer, sondern eine Liste von Zahlen, die mit der Anzahl der Buchstaben in der Zeichenkette übereinstimmen. Für das Wort ‘Beyonce’ erhalten wir statt einer 6, wenn wir len wrapped in range() verwenden, [1,2,3,4,5,6].

keys = [m for m in range(len("Beyonce"))] 
print(keys) 
#[0, 1, 2, 3, 4, 5, 6]

Um enumerate() in einem Listenverständnis zu verwenden, packe die Variablen in eine Klammer, damit Python sie identifizieren kann. Dies ist auch die Syntax, die für zip() zu verwenden ist.

mynewlist = ['boat', 'ship', 'yacht'] 
list = [(e, f) for e, f in enumerate(mynewlist)] 
print(list) 
#[(0, 'boat'), (1, 'ship'), (2, 'yacht')] 

Slicing mit Listenverständnis

Slicing ist eine Indexierungsoperation, bei der Indizes von veränderbaren Sequenzen modifiziert oder verschoben werden. Mit Slicing können Substrings oder Untermengen von einem gegebenen Wert erhalten werden. Wir können Slicing mit Listenverständnis wie folgt verwenden, um das Wort “Super Star” zu erhalten:

word = "I am a SuperStar" 
words = [word[7:12] + " " + word[12:] for i in range(len(word))] 
print(words<i>)  
#Super  Star

Mit Indexierung und Listenverständnis können wir die ersten Buchstaben oder ein Akronym einer Liste von Zeichenketten erhalten.

who = [acry[0] for acry in ("World", "Health", "Organization") ] 
print (who) 
#['W', 'H', 'O']

Bedingte Aussagen mit Listenverständnis

Das Listenverständnis ist auch daher so effektiv, weil es die Stärken von bedingten Aussagen und Iterationen gleichzeitig ausnutzt und berücksichtigt. Die Standardsyntax für das Listenverständnis beinhaltet auch if-Bedingungen

[expression for item in list if conditional]

IF-Bedingungen mit Listenverständnis benutzen

Eine If-Anweisung wird verwendet, wenn es eine Bedingung gibt, die erfüllt werden muss. Wir werden uns die längere Syntax und die Syntax des Listenverständnisses ansehen.

for x in range(20): 
  if x%2==0: 
    print(x)

Mit Listenverständnis wird daraus:

number = [a for a in range(20) if a%2 == 0] 
print(number)

Die Ausgabe des obigen Codes zeigt einen Bereich von geraden Zahlen von zwei bis zwanzig an.

IF..ELSE mit Listenverständnis benutzen

IF..Else-Aussagen stellen zwei Bedingungen an das System. Wenn du nicht A machen kannst, dann solltest du B machen. Das Listenverständnis berücksichtigt dies in seiner Syntax. Hier ist ein Beispiel dafür:

obj = ["Even" if i % 2 == 0 else "Odd" for i in range(10)] 
print(obj) 
#['Even', 'Odd', 'Even', 'Odd', 'Even', 'Odd', 'Even', 'Odd', 'Even', 'Odd']

In nur einer Zeile, in einem Zahlenbereich von eins bis zehn, haben wir die geraden und ungeraden Zahlen mit Hilfe des Listenverständnisses identifizieren können.

In diesem Beispiel steht die if-Bedingung nicht mehr am Ende der Anweisung. Je komplexer die Bedingung wird, desto flexibler wird die Syntax. Das macht sie lesbarer und effizienter.

ELIF mit Listenverständnis benutzen

ELIF wird verwendet, wenn normalerweise zwei oder mehr Bedingungen zu erfüllen sind. Die Bedingungen können je nach Anzahl der zu erfüllenden Bedingungen immer weiter gehen. Fizzbuzz, die beliebte Programmieraufgabe ist das perfekte Beispiel für ein ELIF-Problem, das mit Listenverständnis gelöst werden kann.

In Fizzbuzz wird Fizz ausgegeben, wenn eine Zahl durch 3 teilbar ist. Wenn sie durch 5 teilbar ist, gibt sie Buzz aus. Wenn sie sowohl durch 3 als auch durch 5 teilbar ist, gibt sie Fizzbuzz aus. Wenn eine Zahl jedoch keinem dieser Kriterien entspricht, gibt sie nur die Zahl aus.

Wenn wir nur Schleifen mit Bedingungen verwenden, müssten wir Zeilen und Zeilen von Iterationen wie den folgenden schreiben,

def fizz(fizzbuzz): 
for fizzbuzz in range(fizzbuzz): 
  if fizzbuzz % 3 == 0 and fizzbuzz % 5 == 0: 
    print("fizzbuzz") 
      continue 
  elif fizzbuzz % 3 == 0: 
    print("fizz") 
      continue 
  elif fizzbuzz % 5 == 0: 
    print("buzz") 
      continue 
  else: 
    print(fizzbuzz)     
fizz(16) 
#['fizzbuzz', 1, 2, 'fizz', 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz']

Aber dieser Code kann mit Listenverständnis kürzer gemacht werden. Das sieht dann so aus:

fizzbuzz = ['fizzbuzz' if (num%3==0 and num%5==0) else 'fizz' if (num%3 ==0) else 'buzz' if (num%5 ==0) else num for num in range(16) ] 
print(fizzbuzz)
#['fizzbuzz', 1, 2, 'fizz', 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz']

Bedenke: Wenn man elif-Bedingungen im Listenverständnis schreibt, sollte die letzte Bedingung zuerst geschrieben werden, sonst wird sie übersehen. Wenn zum Beispiel der Fizzbuzz-Code geschrieben wird, dann wird er so geschrieben:

fizzbuzz = ['fizz' if num%3==0 else 'buzz' if num%5 ==0 else 'fizzbuzz' if num%3==0 and num%5==0 else num for num in range(16)] 
print(fizzbuzz)

Die Ausgabe wird folgendermaßen aussehen:

# ['fizz', 1, 2, 'fizz', 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizz']

0 und 15 sollten ‘fizzbuzz’ anzeigen, doch sie wurden durch die Bedingung für num % 3 == 0 außer Kraft gesetzt. 

Verschachtelte if-Anweisungen mit Listenverständnis verwenden

Verschachtelte if-Anweisungen sind if-Anweisungen in einer anderen if-Anweisung.

adj = ['red', 'big', 'tasty'] 
fruits = ['apple', 'cherry', 'banana'] 
  for x in adj: 
    for y in fruits: 
      print(x, y)

Im Listenverständnis können so verschachtelte Bedingungen in einer Zeile wie folgt geschrieben werden:

adj = ['red', 'big', 'tasty'] 
fruits = ['apple', 'cherry', 'banana']
newadj = [(y, x) for y in adj for x in fruits]
print(newadj)
# [('red', 'apple'), ('red', 'cherry'), ('red', 'banana'), ('big', 'apple'), ('big', 'cherry'), ('big', 'banana'), ('tasty', 'apple'), ('tasty', 'cherry'), ('tasty', 'banana')] 

Listenverständnis VS. Map(), Reduce() und Filter()

Map(), Filter() und Reduce() Funktionen sind in Python weit verbreitet und werden viel genutzt. Die hier gezeigten Codeausschnitte können auch mit map(), reduce(), filter() umgeschrieben werden. Zum Beispiel können wir map benutzen, um die Zahlen in der Liste mit 10 zu multiplizieren

nos = [2,3,4,5,6,7,8,9] 
newnos = map(lambda x: x * 10, my_list) 
print(list(newnos))
# [20, 30, 40, 50, 60, 70, 80, 90] 

Und wir können Filter verwenden, um nur gerade Zahlen aus einer Liste anzuzeigen.

nos = [1,2,3,4,5,6,7,8,9,10]  
is_even = lambda x: x % 2 == 0 
# using filter  
new_even_nos = list(filter(is_even, lis1))  
print(new_even_nos)

Das Listenverständnis hat gegenüber den oben genannten Funktionen Vorteile. Es ist schneller, prägnanter, lesbarer, vielseitiger und muss nicht an Lambda-Funktionen gebunden sein. Es wird für eher pythonische Programmieren auch in den Python Style Guidelines empfohlen. 

Dinge, die man über Listenverständnis wissen sollte

  • Die Ausgabe ist immer eine Liste
  • Listenverständnisse sind normalerweise einzeilig. Sie werden also normalerweise in einer Zeile geschrieben, um Listen kürzer und prägnanter zu schreiben.
  • Man macht es sich damit nicht leichter. Sie sind eigentlich pythonisch und werden in den Python Style Guidelines empfohlen.
  • Sie sind in eckigen Klammern geschrieben - [ ].
  • Eine Überbeanspruchung des Listenverständnisses kann einen Code unlesbar machen. Das untergräbt gleichzeitig eine der größten Stärken des Listenverständnisses. Es könnte auch zu Problemen mit der Effizienz und Verwaltbarkeit des Codes führen.
  • Sie sind schneller als for-Schleifen.
  • Listenverständnis kann mit if…else-Anweisungen, Schleifen, verschachtelten Schleifen und bedingungslosen Anweisungen verwendet werden.

Wann man Listenverständnis anwenden sollte

  • Für kurze, prägnante und einfache Logik. Komplexe Bedingungen führen zu Problemen, wenn sie mit Listenverständnis verwendet werden.
  • Für einfache Datensätze. Die Verwendung eines Generators funktioniert am besten für große Datensätze.
  • Wenn du eine Funktion nicht deklarieren musst. Maps werden besser mit Funktionen verwendet.

Abschließende Worte

Listenverständnis zu verstehen, wird dir dabei helfen, besseren pythonischen Code zu schreiben. Viele neigen jedoch auch häufig dazu, Listenverständnis zu viel und unangemessen zu verwenden. Darauf muss man definitv achten. Trotzdem machen Geschwindigkeit, Lesbarkeit und Leichtigkeit des Listenverständnisses es natürlich zu einer bemerkenswerten Alternative für die Lambda-Funktionen, map(), reduce() und filter().

Autor

David Dorr, Leiter Abt. Online Handel

David ist der Leiter der Abteilung Online Handel bei CodeCoda, wo er mehrere Teams von E-Commerce-Spezialisten leitet. In seiner vorherigen Rolle als Deep-Learning-Datenwissenschaftler für die London Metropolitan Police entwickelte er im Rahmen der Crime Prediction-Initiative Deep-Learning-NLP-Algorithmen. Anschließend wechselte er, um KI mit E-Commerce zu kombinieren.
1996 erhielt er einen B.Sc in Physik von der University of Surrey, Guildford. Mit diesem wissenschaftlichen Hintergrund wechselte er relativ früh in seinem Leben zu Neuronalen Netzen und E-Commerce und war seitdem fasziniert davon, was KI und maschinelles Lernen für den Online-Handel leisten können.