Hinweis : Dies war ein Fehler in der Behandlung von yieldVerstä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_FUNCTIONerstellt das Funktionsobjekt aus dem Codeobjekt). Mit der .co_consts[0]Referenz können wir das für den Ausdruck generierte Codeobjekt sehen, das YIELD_VALUEgenau wie eine Generatorfunktion verwendet wird.
Als solches yieldfunktioniert der Ausdruck in diesem Kontext, da der Compiler diese als Funktionen in Verkleidung betrachtet.
Dies ist ein Fehler; yieldhat keinen Platz in diesen Ausdrücken. Die Python- Grammatik vor Python 3.7 erlaubt dies (weshalb der Code kompilierbar ist), aber die yieldAusdrucksspezifikation zeigt, dass die Verwendung yieldhier 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 yieldund yield fromwird ein SyntaxErrorin 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 -3Befehlszeilenschalter 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 SyntaxErrorAusnahme, 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 yieldeines Listenverständnisses und yieldeines Generatorausdrucks ergeben sich aus den Unterschieden bei der Implementierung dieser beiden Ausdrücke. In Python 3 verwendet ein Listenverständnis LIST_APPENDAufrufe, 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_VALUEOpcode 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_VALUEOpcode 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 yieldRü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 yieldund Sie erhalten Nonedas zweite Mal.
Für das Listenverständnis wird dann die beabsichtigte listObjektausgabe weiterhin zurückgegeben, Python 3 sieht dies jedoch als Generator an, sodass der Rückgabewert stattdessen als Attribut an die StopIterationAusnahme angehängt valuewird:
>>> 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 NoneObjekte sind die Rückgabewerte aus den yieldAusdrü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 yieldRückgabewerte weiterhin zum beabsichtigten Wörterbuch oder zum festgelegten Objekt hinzugefügt, und der Rückgabewert wird zuletzt "ausgegeben", anstatt an die StopIterationAusnahme 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-atominnerhalb eines Ausdrucks (innerhalb einer Generatorfunktion) zulässig ist. Dies könnte noch problematischer sein, wenn dasyield-atomirgendwie falsch implementiert wird.