2015-10-05 16 views
5

Sto sperimentando 2 funzioni che emulano il codice zip integrato in Python 2.xe 3.x. Il primo restituisce una lista (come in Python 2.x) e la seconda è una funzione generatore che restituisce un pezzo del suo set di risultati alla volta (come in Python 3.x):L'espressione del generatore causa l'interruzione di Python

def myzip_2x(*seqs): 
    its = [iter(seq) for seq in seqs] 
    res = [] 
    while True: 
     try: 
      res.append(tuple([next(it) for it in its])) # Or use generator expression? 
      # res.append(tuple(next(it) for it in its)) 
     except StopIteration: 
      break 
    return res 

def myzip_3x(*seqs): 
    its = [iter(seq) for seq in seqs] 
    while True: 
     try: 
      yield tuple([next(it) for it in its])   # Or use generator expression? 
      # yield tuple(next(it) for it in its) 
     except StopIteration: 
      return 

print(myzip_2x('abc', 'xyz123'))     
print(list(myzip_3x([1, 2, 3, 4, 5], [7, 8, 9]))) 

Questo funziona bene e dà l'uscita prevista del zip built-in:

[('a', 'x'), ('b', 'y'), ('c', 'z')] 
[(1, 7), (2, 8), (3, 9)] 

Poi ho pensato di sostituire l'elenco di comprensione nei tuple() chiamate con il suo (quasi) espressione equivalente del generatore, eliminando le parentesi quadre [] (perché crea una lista temporanea usando la comprensione quando il generatore dovrebbe andare bene per l'exp iterabile ect by tuple(), giusto?)

Tuttavia, questo causa l'interruzione di Python. Se l'esecuzione non viene interrotta utilizzando CtrlC (in IDLE su Windows), alla fine si fermerà dopo alcuni minuti con un'eccezione (prevista) MemoryError.

Il debug del codice (utilizzando PyScripter per esempio) ha rivelato che l'eccezione StopIteration non viene mai sollevata quando si utilizza l'espressione del generatore. La prima chiamata esempio di cui sopra per myzip_2x() continua ad aggiungere tuple vuote res, mentre il secondo esempio chiamata a myzip_3x() produce tuple (1, 7), (2, 8), (3, 9), (4,), (5,), (), (), (), ....

Mi manca qualcosa?

E una nota finale: lo stesso comportamento appeso appare se its diventa un generatore (utilizzando its = (iter(seq) for seq in seqs)) nella prima linea di ciascuna funzione (quando Lista comprehensions vengono utilizzati nella chiamata tuple()).

Edit:

Grazie @Blckknght per la spiegazione, avevi ragione. This message fornisce maggiori dettagli su ciò che sta accadendo utilizzando un esempio simile alla funzione generatore di cui sopra. In conclusione, l'utilizzo di espressioni generatrici come questo funziona solo in Python 3.5+ e richiede l'istruzione from __future__ import generator_stop nella parte superiore del file e la modifica di StopIteration con RuntimeError sopra (di nuovo, quando si utilizzano le espressioni del generatore anziché le liste di comprensione).

Edit 2:

Quanto alla nota finale sopra: se its diventa un generatore (usando its = (iter(seq) for seq in seqs)) sosterrà solo un'iterazione - perché generatori sono one-shot iteratori. Pertanto si esaurisce la prima volta che si esegue il ciclo while e nei cicli successivi si ottengono solo tuple vuote.

risposta

2

Il comportamento che stai vedendo è un bug. Deriva dal fatto che un'eccezione StopIteration che gorgoglia da un generatore non è distinguibile dal generatore che esce normalmente. Ciò significa che non è possibile racchiudere un loop su un generatore con try e except e cercare StopIteration per interrompere l'accesso al ciclo, poiché la logica del ciclo consumerà l'eccezione.

PEP 479 propone una correzione per il problema, cambiando la lingua per trasformare un StopIteration non occupato in un generatore in RuntimeError prima di fare il bagno. Ciò consentirà al tuo codice di funzionare (con una piccola modifica al tipo di eccezione che si cattura).

Il PEP è stato implementato in Python 3.5, ma per preservare la compatibilità con le versioni precedenti, il comportamento modificato è disponibile solo se lo si richiede inserendo from __future__ import generator_stop nella parte superiore dei file. Il nuovo comportamento sarà abilitato di default in Python 3.7 (Python 3.6 utilizzerà di default il vecchio comportamento, ma potrebbe emettere un avviso se la situazione si presentasse).

0

Quando si esegue:

tuple([next(it) for it in its]) 

si sono prima creazione di una lista, poi passarlo a tuple(). Se l'elenco non può essere creato perché viene sollevato StopIteration, l'elenco non viene creato e l'eccezione viene propagata.

Ma quando lo fai:

tuple(next(it) for it in its) 

si sta costruendo un generatore e passando direttamente al tuple(). Il costruttore di tuple utilizzerà il generatore come un iteratore: ad esempio, visualizzerà gli elementi fino a quando non viene generato lo StopIteration.

Cioè, StopIteration viene rilevato da tuple() e non viene propagato.

Un generatore che solleva immediatamente StopIteration viene convertito in una tupla vuota.

0

Non ne sono veramente sicuro, ma sembra che tu abbia generatori annidati e quello esterno viene rilevato da StopIteration generato da interno.

Considerate questo esempio:

def gen(its): 
    for it in its: 
     yield next(it) # raises StopIteration 

tuple(gen(its)) # doesn't raises StopIteration 

Si fa qualcosa uguale a quello che fa la vostra versione.

2

Quanto segue è un'ipotesi basata sul comportamento di runtime di questi codici, non sul riferimento al linguaggio Python o sull'implementazione di riferimento.

L'espressione tuple(next(it) for it in its) è equivalente a tuple(generator) dove generator = (next(it) for it in its). Il costruttore tuple è concettualmente equivalenti ai seguenti codici:

def __init__(self, generator): 
    for element in generator: 
     self.__internal_array.append(element) 

Poiché l'istruzione for cattura qualsiasi StopIteration come un segno di stanchezza, quando il generatore solleva StopIteration perché next(it) solleva, la dichiarazione for semplicemente intercettarla e pensa che il generatore sia esaurito. Questo è il motivo per cui il ciclo non termina mai e vengono aggiunte le tuple vuote: l'eccezione non fa mai scoppiare il costruttore tuple.

La lista di comprensione, [next(it) for it in its], d'altra parte, sono concettualmente equivalente a

result = [] 
for it in its: 
    result.append(next(it)) 

Così il StopIteration non e 'colto dalla dichiarazione for.

Questo esempio mostra un'interessante differenza non banale tra la comprensione letterale e la chiamata del costruttore con un'espressione generatore. Lo stesso accadrà se si utilizza list(next(it) for it in its rispetto a [next(it) for it in its].

+0

Posso confermare la tua ipotesi eseguendo codici al di fuori di qualsiasi loop o funzione. –

+0

Grazie per una buona spiegazione concettuale. – John

Problemi correlati