Perché gli iteratori in Python sollevano un'eccezione?


48

Ecco la sintassi per gli iteratori in Java (sintassi un po 'simile in C #):

Iterator it = sequence.iterator();

while (it.hasNext()) {
    System.out.println(it.next());
}

Questo ha senso. Ecco la sintassi equivalente in Python:

it = iter(sequence)
while True:
    try:
        value = it.next() 
    except StopIteration:
        break
    print(value)

Pensavo che le eccezioni dovevano essere usate solo in circostanze eccezionali.

Perché Python utilizza le eccezioni per interrompere l'iterazione?


Risposte:


50

C'è un modo molto Pythonic per scrivere quell'espressione senza scrivere esplicitamente un blocco try-tranne per un StopIteration:

# some_iterable is some collection that can be iterated over
# e.g., a list, sequence, dict, set, itertools.combination(...)

for value in some_iterable:
    print(value)

Puoi leggere i PEP pertinenti 234 255 se vuoi saperne di più dietro perché è StopIterationstato introdotto e la logica dietro gli iteratori.

Un principio generale in Python è avere un modo per fare qualcosa (vedi import this), e preferibilmente il suo bello, esplicito, leggibile e semplice, che il metodo pitonico soddisfa. Il tuo codice equivalente è necessario solo in quanto Python non fornisce agli iteratori una hasNextfunzione membro; preferendo che le persone passino direttamente attraverso gli iteratori (e se hai bisogno di fare qualcos'altro per provare a leggerlo e cogliere un'eccezione).

Questa cattura automatica di StopIterationun'eccezione alla fine di un iteratore ha senso ed è un analogo del EOFErrorrilancio se leggi oltre la fine del file.


6
Il modo "Pythonic" sembra molto più simile a "per valore in sequenza:" anziché "per valore in iter (sequenza):" .. Aggiorna post?
Yam Marcovic,

15
@Yam: sono d'accordo. Non è pitonico prendere una sequenza esistente e convertirla in un iteratore solo per applicare un ciclo for ad esso; la sequenza è già iterabile, quindi la conversione di a listin a listiteratorè inutile. Ho mantenuto la prima linea solo per seguire il punto di partenza di NullUserException, per spiegare come si dovrebbe loop su un iteratore, che è lo stesso modo in cui si deve essere ripetuto su ogni iterabile ( list, set, str, tuple, dict, file, generator, etc.). Avrei potuto fare qualcosa di simile it = itertools.combinations("ABCDE", 2)per ottenere un esempio migliore di un iteratore significativo.
dr jimbob,

1
it = iter(sequence)non è necessario.
Caridorc,

2
@Caridorc - Se leggi i commenti, verrai indirizzato al punto. Non è necessario ed è stato fatto per seguire il punto di partenza della domanda (dove si stavano chiedendo esplicitamente iterators) e devi itergenerare esplicitamente un iterator(try type([])( list) vs type(iter([]))( listiterator)).
dr jimbob,

//, @drjimbob, sollevi un punto eccellente nel secondo commento a questa domanda. Sono un po 'nuovo con le funzionalità avanzate di iterables e non lo avrei colto se non avessi letto i commenti. Penso che sarebbe utile a molti di noi poveri anime autodidattici se potessimo vedere quel punto su come la domanda stessa potesse essere cambiata in "The Python Way" come la prima, parte principale della risposta.
Nathan Basanese,

28

Il motivo per cui python utilizza un'eccezione per interrompere un'iterazione è documentato in PEP 234 :

È stato messo in dubbio se un'eccezione per segnalare la fine dell'iterazione non sia troppo costosa. Sono state proposte diverse alternative per l'eccezione StopIteration: un valore speciale End per segnalare la fine, una funzione end () per verificare se l'iteratore è terminato, anche riutilizzando l'eccezione IndexError.

  • Un valore speciale ha il problema che se una sequenza contiene mai quel valore speciale, un ciclo su quella sequenza terminerà prematuramente senza alcun avviso. Se l'esperienza con stringhe C con terminazione null non ci ha insegnato i problemi che ciò può causare, immagina il problema che uno strumento di introspezione di Python avrebbe ripetuto su un elenco di tutti i nomi incorporati, supponendo che lo speciale valore finale fosse un nel nome!

  • La chiamata di una funzione end () richiederebbe due chiamate per iterazione. Due chiamate sono molto più costose di una chiamata più un test per un'eccezione. Soprattutto i tempi critici per il loop possono eseguire test molto economici per un'eccezione.

  • Riutilizzare IndexError può causare confusione perché può essere un vero errore, che potrebbe essere mascherato chiudendo il ciclo prematuramente.

Nota: il modo idiomatico di Python per eseguire il loop su una sequenza è il seguente:

for value in sequence:
    print (value)

20

È una differenza nella filosofia. La filosofia progettuale Pythonic è EAFP :

È più facile chiedere perdono che permesso. Questo stile di codifica Python comune presuppone l'esistenza di chiavi o attributi validi e rileva eccezioni se l'assunzione risulta falsa. Questo stile pulito e veloce è caratterizzato dalla presenza di molte trye exceptaffermazioni. La tecnica contrasta con lo stile LBYL comune a molte altre lingue come C ...


7

È solo che l'implementazione Java ha un hasNext()metodo in modo da poter verificare la presenza di un iteratore vuoto prima di eseguire un next(). Quando si chiama next()un iteratore Java senza elementi rimanenti, NoSuchElementExceptionviene generato un .

Così efficacemente, puoi fare un try..catch in Java come try..except in Python. E sì, come per una risposta precedente, la filosofia è molto importante nel mondo Pythonic.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.