List-Commits in Python sind leistungsfähiger als Sie vielleicht denken / Sudo Null IT News

In Python sind Listenverständnisse (und Listenverständnisse) wunderbare Mechanismen, die Code erheblich vereinfachen können. Sie werden jedoch meistens in Form einer einzelnen for-Schleife und möglicherweise einer einzelnen if-Bedingung verwendet. Und das ist alles. Aber wenn Sie versuchen, ein wenig tiefer in das Thema einzudringen, werden Sie feststellen, dass Pythons Listenverständnis viel mehr zu bieten hat, als Sie vielleicht denken, Fähigkeiten, über die Sie zumindest etwas lernen können.

Mehrere Bedingungen

Es ist bekannt, dass Sie zum Filtern der Ergebnisse der Listenaufnahme die bedingte if-Anweisung verwenden können. Wenn es um eine einfache Listenaufnahme geht, reicht in der Regel ein einziges if aus, um ein bestimmtes Ziel zu erreichen. Was ist, wenn wir eine verschachtelte Bedingung brauchen?

Werte = [True, False, True, None, True]
drucken([‘yes’ if v is True else ‘no’ if v is False else ‘unknown’ for v in values]) # [‘yes’, ‘no’, ‘yes’, ‘unknown’, ‘yes’]

# Der obige Code entspricht dem: result = []
für v in Werten: wenn v True ist: result.append(‘yes’) else: wenn v false ist: result.append(‘no’) else: result.append(‘unknown’) print(result) # [‘yes’, ‘no’, ‘yes’, ‘unknown’, ‘yes’]

Es ist möglich, ein bedingtes Konstrukt mit “bedingten Ausdrücken” oder, wie sie allgemein genannt werden, ternären Operatoren zu verschachteln. Diese Entscheidung kann nicht in jeder Hinsicht angenehm genannt werden. Wenn Sie darauf zurückgreifen, müssen Sie entscheiden, ob ein paar eingesparte Codezeilen es wert sind, einen eher „trostlosen“ Einzeiler zu verwenden.

Bei der Aufnahme von Listen können Sie zusätzlich zur Verwendung komplexer bedingter Strukturen verschachtelte if-Anweisungen verwenden:

drucken([i for i in range(100) if i > 10 if i < 20 if i % 2]) # [11, 13, 15, 17, 19]

# Der obige Code entspricht dem: result = []
for i in range(100): if i > 10: if i < 20: if i % 2: result.append(i) print(result) # [11, 13, 15, 17, 19]

Wenn Sie sich den ausführlichen Code oben ansehen, wird deutlich, dass es nicht notwendig ist, ihn so zu schreiben. Aber die Python-Syntax erlaubt es.

Einer der Gründe, warum man sich für diesen Ansatz entscheiden könnte, hängt mit der Lesbarkeit des Codes zusammen:

drucken([i for i in range(100)
if i > 10
if i < 20
if i % 2])

Vermeidung von sich wiederholenden Berechnungen

Nehmen wir an, wir haben ein Listenverständnis, das eine “schwere” Funktion sowohl beim Überprüfen der Bedingung als auch im Schleifenkörper aufruft:

def func(val): # Schwere Berechnungen… return val > 4 Werte = [1, 4, 3, 5, 12, 9, 0]
drucken([func(x) for x in values if func(x)]) # Ineffizient # [True, True, True]

Dieser Ansatz ist ineffizient, da er zu einer Verdoppelung der Rechenzeit führt. Gibt es eine Möglichkeit, dies zu beheben? Verschachtelte Listenverständnisse helfen uns, dieses Problem zu lösen!

drucken([y for y in (func(x) for x in values) if y]) # Effektiv # [True, True, True]

Ich möchte betonen, dass der obige Code keine Doppelschleife ist. In diesem Beispiel erstellen wir einen Generator innerhalb der umschließenden Liste, der von der äußeren Schleife verwendet wird. Wenn es Ihnen schwerfällt, dies zu lesen, kann der sogenannte „Walross“-Operator eine Alternative zu einem solchen Code werden:

drucken([y for x in values if (y := func(x))])

Hier wird func nur einmal aufgerufen, wodurch eine lokale Variable y erstellt wird, die in anderen Teilen des Ausdrucks verwendet werden kann.

Ausnahmebehandlung

Während Listenverständnisse normalerweise für einfache Aufgaben verwendet werden – wie etwa die Verarbeitung jedes Elements einer Liste mit einer Funktion – ist es möglich, dass eine Ausnahme innerhalb eines Listenverständnisses ausgelöst wird. Es gibt jedoch keine Standardmethode zum Behandeln von Ausnahmen in Listen-Includes. Wie sein?

def catch(f, *args, handle=lambda e: e, **kwargs): try: Rückgabe f(*args, **kwargs) außer Ausnahme als e: return handle(e) values ​​= [1, “text”, 2, 5, 1, “also-text”]
drucken([catch(int, value) for value in values]) drucken([catch(lambda: int(value)) for value in values]) # Alternative Syntax # [
# 1,
# ValueError(“invalid literal for int() with base 10: ‘text'”),
# 2,
# 5,
# 1,
# ValueError(“invalid literal for int() with base 10: ‘also-text'”)
# ]

Wir brauchen eine Handler-Funktion, die Ausnahmen innerhalb der umschließenden Liste abfängt. Wir haben eine Fangfunktion erstellt, die eine andere Funktion als Argument akzeptiert. Wenn eine Ausnahme innerhalb von catch geworfen wird, wird eine Ausnahme zurückgegeben.

Dies ist, da hier eine Hilfsfunktion verwendet wird, keine ideale Lösung. Aber das ist das Beste, was wir tun können, da der Vorschlag (PEP 463), dessen Autoren versuchten, syntaktische Konstruktionen zur Lösung dieses Problems anzubieten, wurde abgelehnt.

Frühes Verlassen der Schleife

Eine weitere Einschränkung des Listenverständnisses besteht darin, dass Sie eine Schleife nicht verlassen können. Obwohl dies mit den Standard-Sprachwerkzeugen nicht möglich ist, kann diese Aufgabe durch Erstellen eines kleinen Hacks gelöst werden:

drucken([i for i in iter(iter(range(10)).__next__, 4)]) # [0, 1, 2, 3]

from itertools import takewhile print([n for n in takewhile(lambda x: x != 4, range(10))]) # [0, 1, 2, 3]

Das erste obige Beispiel verwendet ein wenig bekanntes Feature der Iter-Funktion. Das Konstrukt iter(callable, sentinel) gibt einen Iterator zurück, der die Iteration “unterbricht”, sobald der von der aufrufbaren Funktion zurückgegebene Wert gleich dem Wert des Sentinel-Labels ist. Wenn der innere Iter-Block einen Label-Wert (in diesem Beispiel 4) zurückgibt, stoppt die Schleife automatisch.

Solcher Code ist nicht sehr lesbar. Um dasselbe Problem zu lösen, können Sie daher das wunderbare itertools-Modul und die takewhile-Funktion verwenden. Dieser Ansatz wird im zweiten der obigen Beispiele gezeigt.

Ich möchte darauf hinweisen, dass Sie Recht hatten, wenn Sie dachten, dass das Stoppen der Schleife in einem Listenverständnis zuvor möglich war. Vor Python 3.5 konnten Sie eine Hilfsfunktion verwenden, um eine StopIteration-Ausnahme innerhalb einer Listenaufzählung auszulösen. Dies hat sich jedoch mit der Adoption geändert PEP 479.

Tricks (und Hacks)

In den vorherigen Abschnitten haben wir einige der nicht offensichtlichen Merkmale von Listenverständnissen gesehen, die sich beim täglichen Programmieren als nützlich erweisen können oder auch nicht. Schauen wir uns nun einige Tricks (und ein paar Hacks) an, die Sie jetzt anwenden können.

Obwohl einfache, einfache Listeneinbindung ein sehr leistungsfähiges Werkzeug ist, kann es noch leistungsfähiger gemacht werden, indem es mit Bibliotheken wie itertools (wir haben oben darüber gesprochen) oder seiner Erweiterung more-itertools verknüpft wird.

Nehmen wir an, wir müssen eine Reihe fortlaufender Folgen von Zahlen, Datumsangaben, Buchstaben, booleschen Werten oder anderen geordneten Objekten finden. Dieses Problem kann gut gelöst werden, indem aufeinanderfolgende_Gruppen von more-itertools mit einem Listenverständnis verkettet werden:

import datetime # pip install more-itertools import more_itertools files = [
datetime.datetime(2020, 1, 15),
datetime.datetime(2020, 1, 16),
datetime.datetime(2020, 1, 17),
datetime.datetime(2020, 2, 1),
datetime.datetime(2020, 2, 2),
datetime.datetime(2020, 2, 4)
]

Gruppen = [list(group) for group in more_itertools.consecutive_groups(dates, ordering=lambda d: d.toordinal())]
# [
# [datetime.datetime(2020, 1, 15, 0, 0), datetime.datetime(2020, 1, 16, 0, 0), datetime.datetime(2020, 1, 17, 0, 0)]# [datetime.datetime(2020, 2, 1, 0, 0), datetime.datetime(2020, 2, 2, 0, 0)]# [datetime.datetime(2020, 2, 4, 0, 0)]
#]

Es gibt eine Liste von Daten, von denen einige aufeinander folgen und fortlaufende Sequenzen bilden. Wir übergeben die Daten an die Consecutive_groups-Funktion, wobei wir die ordinalen Datumswerte zum Ordnen verwenden. Wir sammeln dann die zurückgegebenen Gruppen in einer Liste unter Verwendung von Listenverständnis.

In Python ist das Zählen der kumulativen Summen von Zahlen sehr einfach zu implementieren. Sie können einfach itertools.accumulate eine Liste übergeben und die Ausgabe sind Summen. Aber was ist, wenn Sie eine solche Operation abbrechen müssen?

von itertools importieren akkumulieren daten = [4, 5, 12, 8, 1, 10, 21]
kumulativ = list(akkumulieren(daten, initial=100)) print(kumulativ) # [100, 104, 109, 121, 129, 130, 140, 161]

drucken([y – x for x, y in more_itertools.pairwise(cumulative)]) # [4, 5, 12, 8, 1, 10, 21]

Mit Hilfe von more_itertools.pairwise sieht die Lösung dieses Problems denkbar einfach aus.

Wie bereits erwähnt, kann der recht neue “Walross”-Operator mit List Comprehensions verwendet werden, um lokale Variablen zu erstellen. Dies kann sich in vielen Situationen als nützlich erweisen. Eine davon ist die Verwendung der Funktionen any() und all().

Die Funktion any() prüft, ob mindestens ein Wert in einem iterierbaren Objekt eine bestimmte Bedingung erfüllt. Die Funktion all() prüft, ob alle diese Werte eine Bedingung erfüllen. Aber was ist, wenn Sie auch den Wert erfassen möchten, der dazu führt, dass any() True zurückgibt (sogenannter „Beweis“), oder den Wert, der all() zum Scheitern bringt (sogenanntes „Gegenbeispiel“)?

Zahlen = [1, 4, 6, 2, 12, 4, 15]

# Gibt nur boolesche Werte zurück, keine Zahlen print(any(number > 10 for number in numbers)) # True print(all(number < 10 for number in numbers)) # False # ---------- -- --------- any((value := number) > 10 for number in numbers) # True print(value) # 12 all((counter_example := number) < 10 for number in numbers) # False print (Gegenbeispiel) #12

Sowohl any() als auch all() verwenden eine reduzierte Auswertungsreihenfolge bei der Verarbeitung der ihnen übergebenen Daten. Das heißt, sie hören auf zu arbeiten, sobald sie den ersten „Beweis“ bzw. das erste „Gegenbeispiel“ finden. Dank dieses Tricks liefert uns die vom „Walross“-Operator erstellte Variable immer den ersten „Beweis“ oder das erste „Gegenbeispiel“.

Ergebnisse

Viele der in diesem Artikel diskutierten Techniken zielen darauf ab, die Möglichkeiten und Grenzen von Listeneinschlüssen zu demonstrieren. Ich finde, das Studium solcher Feinheiten ist ein guter Weg, um ein tieferes Verständnis bestimmter Mechanismen zu erlangen.

Sprache. Auch wenn es um etwas geht, das im Arbeitsalltag vielleicht nicht besonders nützlich ist. Und unter anderem sind solche Dinge einfach interessant und spannend zu verstehen.

In diesem Sinne hoffe ich, dass Sie heute etwas Neues gelernt haben. Und zum Schluss möchte ich Sie vor etwas warnen. Wenn Sie sich entscheiden, so etwas wie komplexe Bedingungen oder Schleifen vorzeitig zu unterbrechen, in Ihren Listeneinschlüssen zu verwenden, wundern Sie sich nicht, wenn Ihre Kollegen anfangen, Sie schief anzusehen.

Oh, und kommen Sie, um mit uns zu arbeiten? 🤗 💰

Wir sind in wunderfund.io tun Algorithmischer Hochfrequenzhandel seit 2014. Hochfrequenzhandel ist ein ständiger Wettbewerb zwischen den besten Programmierern und Mathematikern auf der ganzen Welt. Indem Sie sich uns anschließen, werden Sie Teil dieses spannenden Kampfes.

Wir bieten interessante und herausfordernde Aufgaben in der Datenanalyse und Low-Latency-Entwicklung für begeisterte Forscher und Programmierer. Flexibler Zeitplan und keine Bürokratie, Entscheidungen werden schnell getroffen und umgesetzt.

Jetzt suchen wir Plus-Entwickler, Pythonisten, Dateningenieure und ml-Einsteiger.

Trete unserem Team bei.

Similar Posts

Leave a Reply

Your email address will not be published.