So reduzieren Sie die Speichernutzung und beschleunigen den Python-Code mithilfe von Generatoren

Hallo allerseits. Heute möchten wir eine nützliche Übersetzung teilen, die vor dem Start des Kurses "Web-Entwickler in Python" vorbereitet wurde. Das Schreiben von zeit- und speichereffizientem Code in Python ist besonders wichtig, wenn Sie eine Webanwendung, ein Modell für maschinelles Lernen oder Tests erstellen.



Als ich anfing, Generatoren in Python zu lernen, hatte ich keine Ahnung, wie wichtig sie waren. Sie halfen mir jedoch ständig beim Schreiben von Funktionen während meiner maschinellen Lernphase.


Mit Generatorfunktionen können Sie eine Funktion deklarieren, die sich wie ein Iterator verhält. Sie ermöglichen es Programmierern, schnelle, einfache und übersichtliche Iteratoren zu erstellen. Ein Iterator ist ein Objekt, das wiederholt (geloopt) werden kann. Es wird verwendet, um den Datencontainer zu abstrahieren und ihn wie ein iterierbares Objekt zu verhalten. Ein Beispiel für ein iterierbares Objekt können beispielsweise Zeichenfolgen, Listen und Wörterbücher sein.


Der Generator sieht aus wie eine Funktion, verwendet jedoch das Schlüsselwort yield anstelle von return. Schauen wir uns ein Beispiel an, um es klarer zu machen.


def generate_numbers(): n = 0 while n < 3: yield n n += 1 

Dies ist eine Generatorfunktion. Wenn Sie es aufrufen, wird ein Generatorobjekt zurückgegeben.


 >>> numbers = generate_numbers() >>> type(numbers) <class 'generator'> 

Es ist wichtig zu beachten, wie der Zustand im Körper der Generatorfunktion eingekapselt ist. Sie können mit der integrierten next () -Funktion nacheinander iterieren:


 >>> next_number = generate_numbers() >>> next(next_number) 0 >>> next(next_number) 1 >>> next(next_number) 2 

Was passiert, wenn Sie next () nach Beendigung der Ausführung aufrufen?


StopIteration ist ein integrierter Ausnahmetyp, der automatisch auftritt, sobald der Generator die Rückgabe eines Ergebnisses beendet. Dies ist ein Stoppsignal für die for-Schleife.


Ertragsaussage


Seine Hauptaufgabe besteht darin, den Ablauf der Generatorfunktion so zu steuern, dass er wie eine return-Anweisung aussieht. Wenn eine Generatorfunktion aufgerufen oder ein Generatorausdruck verwendet wird, gibt sie einen speziellen Iterator zurück, der als Generator bezeichnet wird. Um einen Generator zu verwenden, weisen Sie ihn einer Variablen zu. Beim Aufruf spezieller Methoden im Generator wie next () wird der Funktionscode bis yield ausgeführt.


Wenn die yield-Anweisung eingeht, hält das Programm die Funktion an und gibt den Wert an das Objekt zurück, das die Ausführung initiiert hat. (Während return die Ausführung der Funktion vollständig stoppt.) Wenn die Funktion angehalten wird, bleibt ihr Zustand erhalten.


Da wir nun mit Generatoren in Python vertraut sind, vergleichen wir den üblichen Ansatz mit dem Ansatz, bei dem Generatoren in Bezug auf Arbeitsspeicher und Zeitaufwand für die Codeausführung verwendet werden.


Problemstellung


Angenommen, wir müssen eine große Liste von Zahlen (z. B. 1.000.000.000) durchgehen und die Quadrate aller Zahlen, die separat gespeichert werden müssen, in einer anderen Liste speichern.


Üblicher Ansatz


 import memory_profiler import time def check_even(numbers): even = [] for num in numbers: if num % 2 == 0: even.append(num*num) return even if __name__ == '__main__': m1 = memory_profiler.memory_usage() t1 = time.clock() cubes = check_even(range(100000000)) t2 = time.clock() m2 = memory_profiler.memory_usage() time_diff = t2 - t1 mem_diff = m2[0] - m1[0] print(f"It took {time_diff} Secs and {mem_diff} Mb to execute this method") 

Nach dem Ausführen des obigen Codes erhalten wir Folgendes:


 It took 21.876470000000005 Secs and 1929.703125 Mb to execute this method 

Generatoren verwenden


 import memory_profiler import time def check_even(numbers): for num in numbers: if num % 2 == 0: yield num * num if __name__ == '__main__': m1 = memory_profiler.memory_usage() t1 = time.clock() cubes = check_even(range(100000000)) t2 = time.clock() m2 = memory_profiler.memory_usage() time_diff = t2 - t1 mem_diff = m2[0] - m1[0] print(f"It took {time_diff} Secs and {mem_diff} Mb to execute this method") 

Nach dem Ausführen des obigen Codes erhalten wir Folgendes:


 It took 2.9999999995311555e-05 Secs and 0.02656277 Mb to execute this method 

Wie wir sehen können, werden Laufzeit und Arbeitsspeicher erheblich reduziert. Generatoren arbeiten nach einem Prinzip, das als "Lazy Computing" bezeichnet wird. Dies bedeutet, dass sie Prozessor-, Speicher- und andere Rechenressourcen sparen können.


Fazit


Ich hoffe, dass ich in diesem Artikel zeigen konnte, wie mit Generatoren in Python Ressourcen wie Speicher und Zeit gespart werden können. Dieser Vorteil ergibt sich aus der Tatsache, dass die Generatoren nicht alle Ergebnisse im Speicher ablegen, sondern sie im laufenden Betrieb berechnen. Der Speicher wird nur verwendet, wenn wir das Ergebnis der Berechnungen anfordern. Mit Generatoren können Sie auch eine große Menge an Code abstrahieren, der für das Schreiben von Iteratoren erforderlich ist, um die Codemenge zu reduzieren.

Source: https://habr.com/ru/post/de477926/


All Articles