Sie haben vielleicht gehört, dass eine Funktion mit yield in Python als Generator bezeichnet wird.
Lassen Sie uns zunächst die Generatoren beiseite lassen und ein allgemeines Programmierthema verwenden, um das Konzept des Ertrags zu demonstrieren.
So erzeugen Sie die Fibonacci-Folge
Die Fibonacci-Folge ist eine sehr einfache rekursive Folge. Mit Ausnahme der ersten und zweiten Zahl kann jede 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 Zahl von
deffab(max): n, a, b =0, 0, 1 whilen < max: printb a, b =b, a +b n =n +1
Wenn wir fab(5) ausführen, können wir die folgende Ausgabe erhalten:
>>> fab(5)
Es gibt kein Problem mit dem Ergebnis, Aber mit Erfahrung werden Entwickler darauf hinweisen, dass die Verwendung von print zum Drucken von Zahlen direkt in der Fab-Funktion die Funktion weniger wiederverwendbar macht, da die Fab-Funktion None zurückgibt und andere Funktionen die von der Funktion generierte Sequenz nicht erhalten können.
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 umgeschriebene zweite Version der Fab-Funktion:
Listing 2. Geben Sie die ersten N Zahlen der Fibonacci-Folge aus. Zweite Version
deffab(max): n, a, b =0, 0, 1 L =[] whilen < max: L.append(b) a, b =b, a +b n =n +1 returnL
Sie können die folgende Methode zum Ausdrucken verwenden Von der Funktion zurückgegebene fab-Liste:
>>> forn infab(5):
... printn
...
Umgeschriebene Fab-Funktion Anforderungen an die Wiederverwendbarkeit können durch die Rückgabe von List erfüllt werden. 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 Speichernutzung steuern möchten, ist es am besten, List< nicht zu verwenden 🎜>
um Zwischenergebnisse zu speichern, aber iterierbare Objekte zu durchlaufen. Zum Beispiel in Python2. List und der Code: fori inxrange(1000): pass generiert keine Liste mit 1000 Elementen, sondern gibt den nächsten Wert in jeder Iteration zurück, Speicher Platzbedarf sehr wenig. 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 VersionDie Fab-Klasse gibt über next() kontinuierlich die nächste Zahl in der Sequenz zurück, und die Speichernutzung ist immer konstant:
>>> forn inFab(5):
... printn
classFab(object): def__init__(self, max): self.max=max self.n, self.a, self.b =0, 0, 1 def__iter__(self): returnself defnext(self): ifself.n < self.max: r =self.b self.a, self.b =self.b, self.a +self.b self.n =self.n +1 returnr raiseStopIteration()
Verglichen mit Die erste Version und die vierte Version von fab ändern nur print b in yield b, wodurch der iterierbare Effekt erzielt und gleichzeitig die Einfachheit beibehalten wird.
Der Aufruf der vierten Version von fab ist genau derselbe wie der der zweiten Version von fab:
>>> forn infab(5):
deffab(max): n, a, b =0, 0, 1 whilen < max: yieldb # print b a, b =b, a +b n =n +1
yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。
如何判断一个函数是否是一个特殊的 generator 函数?可以利用 isgeneratorfunction 判断:
清单 7. 使用 isgeneratorfunction 判断
>>> frominspect importisgeneratorfunction
>>> isgeneratorfunction(fab)
True
要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator,好比类的定义和类的实例的区别:
清单 8. 类的定义和类的实例
>>> importtypes
>>> isinstance(fab, types.GeneratorType)
False
>>> isinstance(fab(5), types.GeneratorType)
True
fab 是无法迭代的,而 fab(5) 是可迭代的:
>>> fromcollections importIterable
>>> 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 的例子
defread_file(fpath): BLOCK_SIZE =1024 with open(fpath, 'rb') as f: whileTrue: block =f.read(BLOCK_SIZE) ifblock: yieldblock else: return
以上仅仅简单介绍了 yield 的基本概念和用法,yield 在 Python 3 中还有更强大的用法,我们会在后续文章中讨论。
注:本文的代码均在 Python 2.7 中调试通过