Perché non svuotare i file iterabili in python sollevano eccezioni quando provi a scorrere su di loro [duplicato]

2

Trovo che questo comportamento in Python sia piuttosto peculiare e credo che possa portare a molti bug, specialmente se si dispone di una funzione / metodo che accetta un elenco e restituisce un altro elenco dopo aver eseguito alcune operazioni sugli elementi nell'elenco.

x = []

val = (row for row in x) # create a generator
print(next(val)) # will raise StopIteration exception

for i in x:
    if not i:
        raise ValueError('The sequence is empty') # Exception is not raised
    print(i) # does nothing and does not raise an exception

Se provi a scorrere su un generatore con next ed è vuoto o ha raggiunto la fine, viene sollevata un'eccezione StopIteration , ma questo non è lo stesso quando si utilizza un ciclo for per scorrere su un elenco o qualsiasi iterabile in Python.

So che potrei scrivere if not x: per controllare se l'elenco è vuoto ma credo che un iterabile vuoto dovrebbe generare un'eccezione quando provi a scorrere su di esso poiché non vi è alcun beneficio nel ricollegarlo.

Voglio sapere se c'è qualche motivo speciale per questo comportamento nella progettazione della lingua o non sto considerando un altro caso in cui potrebbe essere utile. Penso che lo stesso valga anche per Java .

    
posta danidee 17.02.2016 - 14:08
fonte

3 risposte

4

La cosa che penso che manchi qui è che il StopIteration è ciò che fa per il loop stop. Ecco perché puoi scrivere iteratori personalizzati che funziona in modo trasparente con Python's for loop. Non esegue alcun controllo per vedere se l'iteratore è vuoto e il ciclo for non tiene traccia dello stato dell'iteratore. Questo è un effetto del protocollo iteratore ed è stata una scelta intenzionale nella progettazione del linguaggio. Puoi leggere ulteriori informazioni sul protocollo iteratore sul sito dei documenti di Python , se lo desideri.

Ha anche più senso se pensi al numero di elementi in, ad es. la tua lista corrisponde direttamente al numero di volte in cui il tuo ciclo viene eseguito.

-----------------------------------|
| No. of items | No. times executed|
------------------------------------
|      5       |         5         |
|      4       |         4         |
|      3       |         3         |
|      2       |         2         |
|      1       |         1         |
|      0       |         0         |
------------------------------------

Se il ciclo for ha un involucro speciale un iterabile vuoto, questo invariante andrebbe perso.

Inoltre complicherebbe il protocollo sulla scrittura di iteratori personalizzati, perché dovresti avere un metodo extra per segnalare al ciclo for che il tuo iterabile è vuoto prima dell'inizio dell'iterazione.

Ecco un esempio (stupido) di un iteratore personalizzato che si comporta sempre come se fosse vuoto:

class EmptyIterator():
    def __iter__(self): return self
    def __next__(self): raise StopIteration

for blah in EmptyIterator():
    print('this is never reached')
try:
    next(EmptyIterator())
except StopIteration:
    print('Oh hi, I was empty so I raised this Exception for you.')
    
risposta data 17.02.2016 - 17:44
fonte
8

Questo è strano per me. Perché una raccolta vuota dovrebbe essere trattata in modo diverso?

Forzare il programmatore a controllare se la raccolta è vuota prima di fare qualcosa sarebbe un fardello diffuso e problematico. Peggio ancora, una collezione vuota non è in alcun modo eccezionale .

Personalmente, preferirei l'errore occasionale in cui si verificava un no-op perché ho dimenticato di controllare il caso raro in cui volevo un comportamento diverso su una raccolta vuota piuttosto che un bug occasionale in cui un'eccezione arresta la mia app perché ho dimenticato per dire if empty, do nothing ovunque volevo quel comportamento comune, previsto.

Voglio dire, pensi che la stampa di stringhe vuote dovrebbe generare eccezioni?

    
risposta data 17.02.2016 - 14:14
fonte
1

Per me, l'iterazione su una sequenza vuota cade (approssimativamente) nella stessa categoria dell'aggiunta di 0, o della moltiplicazione per 1. Certo, moltiplicando un valore per 1 (o aggiungendo 0) non ottieni nulla. Ma è un caso speciale utile di "non fa nulla", nel senso che funziona e, nei rari casi in cui abbiamo bisogno di un elemento di identità, è utile non doverlo caso speciale.

Allo stesso modo con le iterazioni. Se ho una sequenza di cose, che ho scelto perché hanno una proprietà speciale, quindi voglio fare qualcosa per ciascuna cosa, è spesso più utile per consentire il caso speciale di "nessuna cosa era speciale" come una cosa normale e semplicemente non fare nulla.

C'è una cosa che non va nel tuo esempio, però. Non è che print i non faccia nulla, è che non viene mai eseguito. Ed è anche per questo che il tuo if not i: non fa nulla. Tuttavia, sarebbe, se x fosse [False] , [None] , [0] e alcuni altri valori che sono considerati falsi in un contesto booleano.

    
risposta data 17.02.2016 - 16:52
fonte