Generatorausdrücke, Set- und Dict-Comprehensions werden zu (Generator-) Funktionsobjekten kompiliert. In Python 3 erhalten List Comprehensions die gleiche Behandlung; Sie sind alle im Wesentlichen ein neuer verschachtelter Bereich.
Sie können das sehen, wenn Sie versuchen, einen Generator Ausdruck zu zerlegen:
>>> dis.dis(compile("(i for i in range(3))", '', 'exec'))
1 0 LOAD_CONST 0 (<code object <genexpr> at 0x10f7530c0, file "", line 1>)
3 LOAD_CONST 1 ('<genexpr>')
6 MAKE_FUNCTION 0
9 LOAD_NAME 0 (range)
12 LOAD_CONST 2 (3)
15 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
18 GET_ITER
19 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
22 POP_TOP
23 LOAD_CONST 3 (None)
26 RETURN_VALUE
>>> dis.dis(compile("(i for i in range(3))", '', 'exec').co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 11 (to 17)
6 STORE_FAST 1 (i)
9 LOAD_FAST 1 (i)
12 YIELD_VALUE
13 POP_TOP
14 JUMP_ABSOLUTE 3
>> 17 LOAD_CONST 0 (None)
20 RETURN_VALUE
Die oben zeigt, dass ein Generator Ausdruck zu einem Codeobjekt kompiliert wird, geladen als Funktion (MAKE_FUNCTION
erstellt die Funktion Objekt aus dem Code-Objekt). Die Referenz .co_consts[0]
lässt uns das Code-Objekt sehen, das für den Ausdruck generiert wurde, und es verwendet YIELD_VALUE
genau wie eine Generatorfunktion.
Als solche funktioniert der Ausdruck yield
in diesem Kontext, da der Compiler diese Funktionen als verkappte Funktionen sieht.
Dies ist ein Fehler; yield
hat in diesen Ausdrücken keinen Platz. Der Python Grammatik vor Python 3.7 erlaubt es (weshalb der Code übersetzbar ist), aber die yield
expression specification zeigt, dass die Verwendung yield
hier nicht wirklich funktionieren:
Die Ausbeute Ausdruck nur dann verwendet wird, wenn ein Generator definieren Funktion und kann daher nur im Rumpf einer Funktionsdefinition verwendet werden.
Dies wurde als Fehler in issue 10544 bestätigt. Die Lösung des Fehlers ist, dass yield
und yield from
raise a SyntaxError
in Python 3.8; in Python 3.7 it raises a DeprecationWarning
, um sicherzustellen, dass der Code mit diesem Konstrukt aufhört. In Python 2.7.15 und höher wird dieselbe Warnung angezeigt, wenn Sie die -3
command line switch Python 3-Kompatibilitätswarnungen aktivieren.
Die 3.7.0b1 Warnung sieht so aus; Warnungen in Fehler gibt Ihnen eine SyntaxError
Ausnahme drehen, wie würden Sie in 3.8:
>>> [(yield i) for i in range(3)]
<stdin>:1: DeprecationWarning: 'yield' inside list comprehension
<generator object <listcomp> at 0x1092ec7c8>
>>> import warnings
>>> warnings.simplefilter('error')
>>> [(yield i) for i in range(3)]
File "<stdin>", line 1
SyntaxError: 'yield' inside list comprehension
Die Unterschiede zwischen dem, wie yield
in einer Liste Verständnis und yield
in einem Generator Ausdruck arbeiten stammen aus den Unterschieden in, wie diese beiden Ausdrücke umgesetzt werden . In Python 3 verwendet ein Listenverständnis LIST_APPEND
Aufrufe, um den Anfang des Stapels zu der Liste hinzuzufügen, die erstellt wird, während ein Generator-Ausdruck stattdessen diesen Wert liefert. Hinzufügen in (yield <expr>)
fügt nur einen weiteren YIELD_VALUE
Opcode entweder:
>>> dis.dis(compile("[(yield i) for i in range(3)]", '', 'exec').co_consts[0])
1 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 13 (to 22)
9 STORE_FAST 1 (i)
12 LOAD_FAST 1 (i)
15 YIELD_VALUE
16 LIST_APPEND 2
19 JUMP_ABSOLUTE 6
>> 22 RETURN_VALUE
>>> dis.dis(compile("((yield i) for i in range(3))", '', 'exec').co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 12 (to 18)
6 STORE_FAST 1 (i)
9 LOAD_FAST 1 (i)
12 YIELD_VALUE
13 YIELD_VALUE
14 POP_TOP
15 JUMP_ABSOLUTE 3
>> 18 LOAD_CONST 0 (None)
21 RETURN_VALUE
Der YIELD_VALUE
Opcode bei Bytecode-Indizes 15 bzw. 12 Extra ist ein Kuckuck im Nest. Also haben Sie für das List-Comprehension-turned-generator eine Ausbeute, die jedes Mal die Spitze des Stapels erzeugt (indem Sie die oberste des Stapels durch den yield
Rückgabewert ersetzen), und für die Generator-Ausdrucksvariante geben Sie die Spitze des Stapels (die ganze Zahl) und ergeben dann wieder, aber jetzt enthält der Stapel den Rückgabewert des yield
und Sie None
, die zweite Zeit.
Für die Liste Verständnis dann wird die beabsichtigte list
Objektausgabe noch zurück, aber Python 3 sieht dies als Generator, so ist der Rückgabewert statt zum StopIteration
exception als value
Attribut gebunden ist:
>>> from itertools import islice
>>> listgen = [(yield i) for i in range(3)]
>>> list(islice(listgen, 3)) # avoid exhausting the generator
[0, 1, 2]
>>> try:
... next(listgen)
... except StopIteration as si:
... print(si.value)
...
[None, None, None]
Diejenigen None
Objekte sind die Rückgabewerte aus den yield
Ausdrücken.
Und um das noch einmal zu wiederholen; Dasselbe gilt für das Wörterbuch und das Verstehen von Python 2 und Python 3; in Python 2 die yield
Rückgabewerte werden den beabsichtigten Wörterbuch noch hinzugefügt oder Objekt gesetzt, und der Rückgabewert wird zuletzt statt an die StopIteration
Ausnahme ‚liefert‘:
>>> list({(yield k): (yield v) for k, v in {'foo': 'bar', 'spam': 'eggs'}.items()})
['bar', 'foo', 'eggs', 'spam', {None: None}]
>>> list({(yield i) for i in range(3)})
[0, 1, 2, set([None])]
Note, das die Sprachspezifikation gemäß dem ' yield-atom' ist innerhalb eines Ausdrucks erlaubt (innerhalb einer Generatorfunktion). Dies könnte noch problematischer sein, wenn das "yield-atom" irgendwie falsch implementiert wird. – skyking
@skyking: das ist was ich sage; Die Grammatik erlaubt es. Der Fehler, auf den ich mich beziehe, versucht, einen 'yield' * als Teil eines Generatorausdrucks innerhalb einer Generatorfunktion * zu verwenden, wobei erwartet wird, dass der' yield' auf die Generatorfunktion und nicht auf den verschachtelten Bereich des Generatorausdrucks zutrifft. –
Wow. Sehr informativ. Also, wenn ich richtig verstanden habe, passierte folgendes: Eine Funktion, die sowohl 'yield' als auch 'return' enthält, sollte, wie dokumentiert, zu einer Generatorfunktion werden, deren 'return'ed Wert in der' StopIteration' Exception landen sollte Bytecode für ein Listenverständnis mit 'yield' innerhalb von Looks (obwohl es nicht beabsichtigt war) genau wie der Bytecode einer solchen Funktion. – zabolekar