Sie haben vielleicht gehört, dass eine Funktion mit yield in Python als Generator bezeichnet wird.
Lassen wir zunächst die Generatoren beiseite und verwenden ein häufiges Programmierproblem, um das Konzept der Ausbeute zu demonstrieren.
So generieren Sie die Fibonacci-Folge
Die Fibonacci-Folge ist eine sehr einfache rekursive Folge. Mit Ausnahme der ersten und zweiten Zahl kann jede beliebige Zahl durch Addition der ersten beiden Zahlen erhalten werden. Die Verwendung eines Computerprogramms zur Ausgabe der ersten N Zahlen der Fibonacci-Folge ist ein sehr einfaches Problem. Viele Anfänger können leicht die folgende Funktion schreiben:
Liste 1. Einfache Ausgabe der ersten N Zahlen der Fibonacci-Folge
def fab(max): n, a, b = 0, 0, 1 while n < max: print b a, b = b, a + b n = n + 1
Wenn wir fab(5) ausführen, können wir die folgende Ausgabe erhalten:
>>> fab(5) 1 1 2 3 5
Es gibt kein Problem mit dem Ergebnis, aber erfahrene Entwickler werden darauf hinweisen, dass die Verwendung von print zum Drucken von Zahlen direkt in der Fab-Funktion zu einer schlechten Wiederverwendbarkeit der Funktion führt, da die Fab-Funktion None zurückgibt und andere Funktionen die von der generierte Sequenz nicht erhalten können Funktion.
Um die Wiederverwendbarkeit der Fab-Funktion zu verbessern, ist es am besten, die Sequenz nicht direkt auszudrucken, sondern eine Liste zurückzugeben. Das Folgende ist die neu geschriebene zweite Version der Fab-Funktion:
Listing 2. Geben Sie die ersten N Zahlen der zweiten Version der Fibonacci-Folge aus
def fab(max): n, a, b = 0, 0, 1 L = [] while n < max: L.append(b) a, b = b, a + b n = n + 1 return L
Sie können die folgende Methode verwenden, um die von der Fab-Funktion zurückgegebene Liste auszudrucken:
>>> for n in fab(5): ... print n ... 1 1 2 3 5
Die neu geschriebene Fab-Funktion kann die Wiederverwendbarkeitsanforderungen erfüllen, indem sie eine Liste zurückgibt. Erfahrenere Entwickler werden jedoch darauf hinweisen, dass der von dieser Funktion während des Betriebs belegte Speicher mit zunehmendem Parameter max. zunimmt. Wenn Sie die Speicherbelegung steuern möchten, ist dies am besten Liste nicht verwenden
Um Zwischenergebnisse zu speichern, iterieren Sie durch iterierbare Objekte. Beispiel: In Python2.x Code:
Listing 3. Iterieren durch iterierbare Objekte
for i in range(1000): pass
führt zur Generierung einer Liste mit 1000 Elementen und des Codes:
for i in xrange(1000): pass
generiert keine Liste mit 1000 Elementen, sondern gibt in jeder Iteration den nächsten Wert zurück und belegt dabei nur sehr wenig Speicherplatz. Weil xrange keine Liste zurückgibt, sondern ein iterierbares Objekt.
Mit iterable können wir die Fab-Funktion in eine Klasse umschreiben, die Iterable unterstützt. Das Folgende ist die dritte Version von Fab:
Listing 4. Die dritte Version
class Fab(object): def __init__(self, max): self.max = max self.n, self.a, self.b = 0, 0, 1 def __iter__(self): return self def next(self): if self.n < self.max: r = self.b self.a, self.b = self.b, self.a + self.b self.n = self.n + 1 return r raise StopIteration()
Die Fab-Klasse gibt über next() kontinuierlich die nächste Zahl in der Sequenz zurück und die Speichernutzung ist immer konstant:
>>> for n in Fab(5): ... print n ... 1 1 2 3 5
Allerdings ist der Code dieser mithilfe von class neu geschriebenen Version weitaus weniger prägnant als der der ersten Version der Fab-Funktion. Wenn wir die Einfachheit der ersten Version der Fab-Funktion beibehalten und gleichzeitig den iterierbaren Effekt erzielen möchten, ist yield praktisch:
Listing 5. Vierte Version mit yield
def fab(max): n, a, b = 0, 0, 1 while n < max: yield b # print b a, b = b, a + b n = n + 1 '''
Im Vergleich zur ersten Version änderte die vierte Version von fab nur print b in yield b, wodurch der iterierbare Effekt erzielt und gleichzeitig die Einfachheit beibehalten wurde.
Der Aufruf der vierten Version von Fab ist genau derselbe wie der der zweiten Version von Fab:
>>> for n in fab(5): ... print n ... 1 1 2 3 5
Einfach ausgedrückt besteht die Funktion von yield darin, eine Funktion mit yield nicht mehr als gewöhnliche Funktion zu behandeln. Der Aufruf von fab(5) führt sie stattdessen nicht aus , es gibt ein iterierbares Objekt zurück! Wenn die for-Schleife ausgeführt wird, führt jede Schleife den Code innerhalb der Fab-Funktion aus. Wenn sie Yield B erreicht, gibt die Fab-Funktion einen Iterationswert zurück. In der nächsten Iteration wird der Code ab der nächsten Anweisung von Yield B ausgeführt. und die Funktion Die lokale Variable sieht genauso aus wie vor der letzten Unterbrechung, sodass die Funktion mit der Ausführung fortfährt, bis Yield erneut angetroffen wird.
Sie können die next()-Methode von fab(5) auch manuell aufrufen (da fab(5) ein Generatorobjekt ist, das über eine next()-Methode verfügt), damit wir den Ausführungsprozess von fab klarer sehen können:
Liste 6. Ausführungsprozess
>>> f = fab(5) >>> f.next() 1 >>> f.next() 1 >>> f.next() 2 >>> f.next() 3 >>> f.next() 5 >>> f.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
Wenn die Funktionsausführung endet, löst der Generator automatisch eine StopIteration-Ausnahme aus, die anzeigt, dass die Iteration abgeschlossen ist. In der for-Schleife muss die StopIteration-Ausnahme nicht behandelt werden und die Schleife endet normal.
Wir können folgende Schlussfolgerungen ziehen:
Eine Funktion mit yield ist ein Generator. Sie unterscheidet sich von einer gewöhnlichen Funktion. Das Generieren eines Generators sieht aus wie ein Funktionsaufruf, führt jedoch keinen Funktionscode aus, bis next() darauf aufgerufen wird (next( wird automatisch in einem for aufgerufen). Schleife) )) vor Beginn der Ausführung. Obwohl der Ausführungsablauf weiterhin entsprechend dem Ablauf der Funktion ausgeführt wird, wird er bei jeder Ausführung einer Yield-Anweisung unterbrochen und ein Iterationswert zurückgegeben. Die nächste Ausführung wird mit der nächsten Yield-Anweisung fortgesetzt. Es sieht so aus, als ob eine Funktion während der normalen Ausführung mehrmals durch yield unterbrochen wird und jede Unterbrechung den aktuellen Iterationswert über yield zurückgibt.
Die Vorteile von yield liegen auf der Hand. Durch das Umschreiben einer Funktion als Generator erhalten Sie die Fähigkeit zur Iteration. Im Vergleich zur Verwendung einer Instanz einer Klasse zum Speichern des Status zur Berechnung des next()-Werts ist der Code nicht nur prägnant, sondern auch Der Ausführungsprozess ist äußerst klar.
如何判断一个函数是否是一个特殊的 generator 函数?可以利用 isgeneratorfunction 判断:
清单 7. 使用 isgeneratorfunction 判断
>>> from inspect import isgeneratorfunction >>> isgeneratorfunction(fab) True
要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator,好比类的定义和类的实例的区别:
清单 8. 类的定义和类的实例
>>> import types >>> isinstance(fab, types.GeneratorType) False >>> isinstance(fab(5), types.GeneratorType) True
fab 是无法迭代的,而 fab(5) 是可迭代的:
>>> from collections import Iterable >>> isinstance(fab, Iterable) False >>> isinstance(fab(5), Iterable) True
每次调用 fab 函数都会生成一个新的 generator 实例,各实例互不影响:
>>> f1 = fab(3) >>> f2 = fab(5) >>> print 'f1:', f1.next() f1: 1 >>> print 'f2:', f2.next() f2: 1 >>> print 'f1:', f1.next() f1: 1 >>> print 'f2:', f2.next() f2: 1 >>> print 'f1:', f1.next() f1: 2 >>> print 'f2:', f2.next() f2: 2 >>> print 'f2:', f2.next() f2: 3 >>> print 'f2:', f2.next() f2: 5
return 的作用
在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。
另一个例子
另一个 yield 的例子来源于文件读取。如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:
清单 9. 另一个 yield 的例子
def read_file(fpath): BLOCK_SIZE = 1024 with open(fpath, 'rb') as f: while True: block = f.read(BLOCK_SIZE) if block: yield block else: return
以上仅仅简单介绍了 yield 的基本概念和用法,yield 在 Python 3 中还有更强大的用法,我们会在后续文章中讨论。
注:本文的代码均在 Python 2.7 中调试通过
Das obige ist der detaillierte Inhalt vonAnalyse der Python-Ertragsnutzung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!