Hinweis : Dies war ein Fehler in der Behandlung von yield
Verständnis- und Generatorausdrücken durch CPython , der in Python 3.8 behoben wurde und in Python 3.7 eine Warnung vor Verfall enthielt. Weitere Informationen finden Sie im Python-Fehlerbericht und in den neuen Einträgen für Python 3.7 und Python 3.8 .
Generatorausdrücke sowie Mengen- und Diktatverständnisse werden zu (Generator-) Funktionsobjekten kompiliert. In Python 3 werden Listenverständnisse gleich behandelt. Sie alle sind im Wesentlichen ein neuer verschachtelter Bereich.
Sie können dies sehen, wenn Sie versuchen, einen Generatorausdruck 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
Das Obige zeigt, dass ein Generatorausdruck zu einem Codeobjekt kompiliert wird, das als Funktion geladen wird ( MAKE_FUNCTION
erstellt das Funktionsobjekt aus dem Codeobjekt). Mit der .co_consts[0]
Referenz können wir das für den Ausdruck generierte Codeobjekt sehen, das YIELD_VALUE
genau wie eine Generatorfunktion verwendet wird.
Als solches yield
funktioniert der Ausdruck in diesem Kontext, da der Compiler diese als Funktionen in Verkleidung betrachtet.
Dies ist ein Fehler; yield
hat keinen Platz in diesen Ausdrücken. Die Python- Grammatik vor Python 3.7 erlaubt dies (weshalb der Code kompilierbar ist), aber die yield
Ausdrucksspezifikation zeigt, dass die Verwendung yield
hier eigentlich nicht funktionieren sollte:
Der Ertragsausdruck wird nur beim Definieren einer Generatorfunktion verwendet und kann daher nur im Hauptteil einer Funktionsdefinition verwendet werden.
Es wurde bestätigt, dass dies ein Fehler in Ausgabe 10544 ist . Die Lösung des Fehlers ist die Verwendung von yield
und yield from
wird ein SyntaxError
in Python 3.8 auslösen ; In Python 3.7 wird ein a ausgelöst,DeprecationWarning
um sicherzustellen, dass Code dieses Konstrukt nicht mehr verwendet. Die gleiche Warnung wird in Python 2.7.15 und höher angezeigt, wenn Sie den -3
Befehlszeilenschalter verwenden, der Python 3-Kompatibilitätswarnungen aktiviert.
Die Warnung 3.7.0b1 sieht folgendermaßen aus. Wenn Sie Warnungen in Fehler umwandeln, erhalten Sie eine SyntaxError
Ausnahme, wie Sie es in 3.8 tun würden:
>>> [(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 der Funktionsweise yield
eines Listenverständnisses und yield
eines Generatorausdrucks ergeben sich aus den Unterschieden bei der Implementierung dieser beiden Ausdrücke. In Python 3 verwendet ein Listenverständnis LIST_APPEND
Aufrufe, um der Liste, die erstellt wird, den Anfang des Stapels hinzuzufügen, während ein Generatorausdruck stattdessen diesen Wert liefert. Durch Hinzufügen wird (yield <expr>)
nur ein weiterer YIELD_VALUE
Opcode hinzugefügt:
>>> 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 den Bytecode-Indizes 15 bzw. 12 ist extra, ein Kuckuck im Nest. Für den Listenverständnis-gedrehten Generator haben Sie also 1 Ausbeute, die jedes Mal die Spitze des Stapels erzeugt (wobei die Spitze des Stapels durch den yield
Rückgabewert ersetzt wird), und für die Generatorausdrucksvariante ergeben Sie die Spitze des Stapels (die Ganzzahl) und dann wieder ergeben , aber jetzt enthält der Stapel den Rückgabewert von yield
und Sie erhalten None
das zweite Mal.
Für das Listenverständnis wird dann die beabsichtigte list
Objektausgabe weiterhin zurückgegeben, Python 3 sieht dies jedoch als Generator an, sodass der Rückgabewert stattdessen als Attribut an die StopIteration
Ausnahme angehängt value
wird:
>>> from itertools import islice
>>> listgen = [(yield i) for i in range(3)]
>>> list(islice(listgen, 3))
[0, 1, 2]
>>> try:
... next(listgen)
... except StopIteration as si:
... print(si.value)
...
[None, None, None]
Diese None
Objekte sind die Rückgabewerte aus den yield
Ausdrücken.
Und um dies noch einmal zu wiederholen; Das gleiche Problem gilt auch für das Wörterbuch- und Set-Verständnis in Python 2 und Python 3. In Python 2 werden die yield
Rückgabewerte weiterhin zum beabsichtigten Wörterbuch oder zum festgelegten Objekt hinzugefügt, und der Rückgabewert wird zuletzt "ausgegeben", anstatt an die StopIteration
Ausnahme angehängt zu werden :
>>> 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])]
yield-atom
innerhalb eines Ausdrucks (innerhalb einer Generatorfunktion) zulässig ist. Dies könnte noch problematischer sein, wenn dasyield-atom
irgendwie falsch implementiert wird.