2015-02-02 16 views
26

Oggi ho trovato un bug che si verificava perché stavo usando next() per estrarre un valore, e 'not found' emette un StopIteration.next() non funziona bene con any/all in python

Normalmente che arrestare il programma, ma la funzione utilizzando next veniva chiamata all'interno di un all() iterazione, quindi il all appena terminato presto e restituito True.

È un comportamento previsto? Ci sono delle guide di stile che aiutano a evitare questo genere di cose?

esempio semplificato:

def error(): return next(i for i in range(3) if i==10) 
error() # fails with StopIteration 
all(error() for i in range(2)) # returns True 
+0

La stessa cosa accade in Python 3. – khelwood

+0

@khelwood grazie, io rimuovere il tag py2.7 – amwinter

+9

Certo. [** all ** * (iterable) * Restituisce True se tutti gli elementi di iterable sono true *** (o se iterable è vuoto) ***.] (https://docs.python.org/2/ library/functions.html # all) –

risposta

24

Mentre questo è il comportamento predefinito nelle versioni di Python fino al 3,6 incluso, è considerato un errore nella lingua e è programmato per cambiare in Python 3.7 in modo che venga sollevata un'eccezione.

Come PEP 479 dice:

L'interazione di generatori e StopIteration è attualmente un po 'sorprendente, e possono nascondere insetti oscuri. Un'eccezione imprevista non dovrebbe comportare un comportamento leggermente alterato, ma dovrebbe causare un traceback rumoroso e facilmente debug. Attualmente, StopIteration generato accidentalmente all'interno di una funzione del generatore verrà interpretato come la fine dell'iterazione dal costrutto del loop che guida il generatore.

Da Python 3.5 in poi, è possibile modificare il comportamento predefinito a quello pianificato per 3.7. Questo codice:

# gs_exc.py 

from __future__ import generator_stop 

def error(): 
    return next(i for i in range(3) if i==10) 

all(error() for i in range(2)) 

... solleva la seguente eccezione:

Traceback (most recent call last): 
    File "gs_exc.py", line 8, in <genexpr> 
    all(error() for i in range(2)) 
    File "gs_exc.py", line 6, in error 
    return next(i for i in range(3) if i==10) 
StopIteration 

The above exception was the direct cause of the following exception: 

Traceback (most recent call last): 
    File "gs_exc.py", line 8, in <module> 
    all(error() for i in range(2)) 
RuntimeError: generator raised StopIteration 

In Python 3.5 e 3.6 senza il __future__ importazione, un messaggio di avviso viene generato.Per esempio:

# gs_warn.py 

def error(): 
    return next(i for i in range(3) if i==10) 

all(error() for i in range(2)) 

$ python3.5 -Wd gs_warn.py 
gs_warn.py:6: PendingDeprecationWarning: generator '<genexpr>' raised StopIteration 
    all(error() for i in range(2)) 

$ python3.6 -Wd gs_warn.py 
gs_warn.py:6: DeprecationWarning: generator '<genexpr>' raised StopIteration 
    all(error() for i in range(2)) 
+0

grazie - ho incrociato le dita sorpresi da StopIteration bubbling non mi ha fatto impazzire – amwinter

+0

Ciò significa che 'all (some_iterator)' e 'mlist = [i for i in some_iterator]; all (mlist)' si comporterebbe in modo diverso? – tdelaney

+0

@tdelaney Sì, se 'some_iterator' è un generatore che solleva' StopIteration'. –

9

Il problema non è nel usando all, è che si dispone di un generatore di espressione come parametro per all. Il StopIteration viene propagato all'espressione del generatore, che in realtà non sa da dove proviene, quindi fa la solita cosa e termina l'iterazione.

Potete vedere questo sostituendo la funzione error con qualcosa che genera direttamente l'errore:

def error2(): raise StopIteration 

>>> all(error2() for i in range(2)) 
True 

L'ultimo pezzo del puzzle è sapere che cosa all fa con una sequenza vuota:

>>> all([]) 
True 

Se hai intenzione di usare direttamente next, dovresti essere pronto a prendere lo StopIteration da solo.

Modifica: bello vedere che gli sviluppatori Python considerano questo un bug e stanno adottando le misure per modificarlo in 3.7.

+0

perché l'errore non viene propagato con 'all'? –

+1

ok - quindi stai dicendo che la soluzione è di non usare 'any' /' all' intorno a tutto ciò che può generare un 'StopIteration'? – amwinter

+0

@PadraicCunningham è catturato dall'espressione del generatore stesso. –

Problemi correlati