Le espressioni del generatore e le regole di set e dict sono compilate per oggetti funzione (generatore). In Python 3, la comprensione delle liste ha lo stesso trattamento; sono tutti, in sostanza, un nuovo ambito nidificato.
Potete vedere questo se si tenta di smontare un generatore di espressione:
>>> 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
Quanto sopra dimostra che un generatore di espressione viene compilata in un oggetto di codice, caricato come una funzione (MAKE_FUNCTION
crea l'oggetto funzione dal oggetto codice). Il riferimento .co_consts[0]
ci consente di vedere l'oggetto codice generato per l'espressione e utilizza YIELD_VALUE
proprio come farebbe una funzione generatore.
Come tale, l'espressione yield
funziona in tale contesto, in quanto il compilatore li vede come funzioni in incognito.
Questo è un bug; yield
non ha posto in queste espressioni. Il pitone grammatica prima Python 3.7 consente (che è il motivo per cui il codice è compilabile), ma il yield
expression specification dimostra che l'utilizzo yield
qui non dovrebbero mai funzionare:
l'espressione yield viene usata solo quando si definisce un generatore e quindi può essere utilizzato solo nel corpo di una definizione di funzione.
È stato riscontrato un errore in issue 10544. La risoluzione del bug è che usando yield
e yield from
sarà raise a SyntaxError
in Python 3.8; in Python 3.7 it raises a DeprecationWarning
per assicurare che il codice si fermi utilizzando questo costrutto. Vedrai lo stesso avvertimento in Python 2.7.15 e versioni successive se usi lo -3
command line switch per attivare gli avvisi di compatibilità con Python 3.
Il 3.7.L'avviso 0b1 ha questo aspetto; trasformando gli avvisi in errori ti dà un'eccezione SyntaxError
, come si farebbe 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
le differenze tra come yield
in una comprensione lista e yield
in un'espressione generatore di operare staminali dalle differenze nel modo in cui queste due espressioni sono implementati . In Python 3 una list comprehension utilizza le chiamate LIST_APPEND
per aggiungere la parte superiore dello stack all'elenco in fase di creazione, mentre un'espressione generatore produce invece quel valore. Aggiungendo (yield <expr>)
appena aggiunge un altro YIELD_VALUE
opcode a uno:
>>> 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
Il YIELD_VALUE
codice operativo a indici bytecode 15 e 12 rispettivamente è aggiunto, un cuculo nel nido. Quindi per il generatore di list-comprehension-turned hai 1 yield producendo la cima dello stack ogni volta (sostituendo la parte superiore della pila con il valore di ritorno yield
), e per la variante di espressione del generatore ottieni la cima della pila (il numero intero) e quindi restituire di nuovo, ma ora lo stack contiene il valore di ritorno dello yield
e si ottiene None
per la seconda volta.
Per l'elenco comprensione allora, il inteso list
uscita oggetto viene ancora restituito, ma Python 3 la considera come generatore in modo che il valore restituito viene invece fissato alla StopIteration
exception come attributo value
:
>>> 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]
Quelli Gli oggetti None
sono i valori restituiti dalle espressioni yield
.
E ripetermelo ancora; questo stesso problema si applica anche al dizionario e alla comprensione dei set in Python 2 e Python 3; in Python 2 i valori di ritorno yield
vengono ancora aggiunti al dizionario previsto o oggetto impostare, e il valore di ritorno è 'ceduto' ultima invece attaccati l'eccezione StopIteration
:
>>> 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])]
nota che secondo le specifiche del linguaggio del ' yield-atom' è permesso all'interno di un'espressione (all'interno di una funzione generatore). Questo potrebbe essere ancora più problematico se il 'yield-atom' è in qualche modo male implementato. – skyking
@skyking: questo è quello che sto dicendo; la grammatica lo consente. L'errore a cui mi riferisco sta tentando di usare un 'yield' * come parte di un'espressione di generatore all'interno di una funzione generatore *, dove l'aspettativa è che il' yield' si applica alla funzione generatore, non all'ambito nested dell'espressione generatore. –
Wow. Davvero molto informativo. Quindi, se ho capito bene, è successo quanto segue: una funzione che contiene sia 'yield' che' return' dovrebbe, come è documentato, diventare una funzione generatore il cui valore 'return'ed dovrebbe finire nell'eccezione' StopIteration', e bytecode per una comprensione delle liste con 'yield' all'interno di look (sebbene non fosse previsto) proprio come il bytecode di tale funzione. – zabolekar