Por que os iteradores no Python geram uma exceção?

48

Aqui está a sintaxe para iteradores em Java (sintaxe semelhante em C #):

Iterator it = sequence.iterator();

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

O que faz sentido. Aqui está a sintaxe equivalente no Python:

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

Eu pensei que as exceções deveriam ser usadas apenas em circunstâncias excepcionais.

Por que o Python usa exceções para interromper a iteração?

NullUserException
fonte

Respostas:

50

Existe uma maneira muito pitônica de escrever essa expressão sem escrever explicitamente um bloco try-exceto para um 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)

Você pode ler as PEPs relevantes 234 255 se quiser saber mais sobre o porquê de ter StopIterationsido introduzido e a lógica por trás dos iteradores.

Um princípio geral em python é ter uma maneira de fazer alguma coisa (ver import this) e, de preferência, ser bonito, explícito, legível e simples, o que o método pitônico satisfaz. Seu código equivalente é necessário apenas porque o python não fornece aos iteradores uma hasNextfunção de membro; preferindo que as pessoas passem pelos iteradores diretamente (e se você precisar fazer outra coisa, tente ler e capturar uma exceção).

Essa captura automática de uma StopIterationexceção no final de um iterador faz sentido e é um análogo do EOFErrorgerado se você ler além do final do arquivo.

dr jimbob
fonte
6
A maneira "Pythonic" com certeza parece muito mais com "valor na sequência:" em vez de "valor na iter (sequência):" .. Atualizar postagem?
Yam Marcovic
15
@Yam: eu concordo. Não é pitônico pegar uma sequência existente e convertê-la em um iterador apenas para aplicar um loop for a ela; a sequência já é iterável; portanto, a conversão de a listpara a listiteratoré inútil. Eu mantive a primeira linha apenas para seguir o ponto de partida do NullUserException, para explicar como você deve loop sobre um iterador, que é a mesma maneira que você deve loop sobre qualquer iterable ( list, set, str, tuple, dict, file, generator, etc.). Eu poderia ter feito algo parecido it = itertools.combinations("ABCDE", 2)para obter um exemplo melhor de um iterador significativo.
dr jimbob
11
it = iter(sequence)Não é necessário.
Caridorc
2
@Caridorc - Se você ler os comentários, seu ponto foi abordado. Não é necessário e foi feito para seguir o ponto de partida da pergunta (onde eles estavam perguntando explicitamente iterators) e você precisa itergerar explicitamente um iterator(try type([])( list) vs type(iter([]))( listiterator)).
dr jimbob
//, @drjimbob, você levanta um ponto excelente no segundo comentário a esta pergunta. Sou um pouco novo nos recursos avançados dos iterables e não entenderia isso se não tivesse lido os comentários. Eu acho que beneficiaria muitos de nós, almas pobres de auto-educação, se pudéssemos ver esse ponto sobre como a pergunta em si poderia ser alterada para "The Python Way" como a primeira parte principal da resposta.
Nathan Basanese 23/03
28

O motivo pelo qual o python usa uma exceção para interromper uma iteração está documentado no PEP 234 :

Foi questionado se uma exceção para sinalizar o final da iteração não é muito cara. Várias alternativas para a exceção StopIteration foram propostas: um valor especial End para sinalizar o final, uma função end () para testar se o iterador está concluído, até mesmo reutilizando a exceção IndexError.

  • Um valor especial tem o problema de que, se uma sequência contiver esse valor especial, um loop sobre essa sequência terminará prematuramente sem nenhum aviso. Se a experiência com cadeias C terminadas em nulo não nos ensinou os problemas que isso pode causar, imagine o problema que uma ferramenta de introspecção Python teria iterando sobre uma lista de todos os nomes internos, assumindo que o valor final especial fosse um em nome!

  • Chamar uma função end () exigiria duas chamadas por iteração. Duas ligações são muito mais caras que uma ligação e um teste para uma exceção. Especialmente o loop for de tempo crítico pode testar muito barato por uma exceção.

  • Reutilizar IndexError pode causar confusão, pois pode ser um erro genuíno, que seria mascarado ao encerrar o loop prematuramente.

Nota: a maneira idiomática do python para fazer um loop sobre uma sequência é assim:

for value in sequence:
    print (value)
lesmana
fonte
20

É uma diferença na filosofia. A filosofia de design Pythonic é EAFP :

Mais fácil pedir perdão do que permissão. Esse estilo comum de codificação Python pressupõe a existência de chaves ou atributos válidos e captura exceções se a suposição for falsa. Este estilo limpo e rápido é caracterizado pela presença de muitos trye exceptdeclarações. A técnica contrasta com o estilo LBYL comum a muitos outros idiomas, como o C ...

Charles E. Grant
fonte
7

Só que a implementação Java possui um hasNext()método para que você possa procurar um iterador vazio antes de executar um next(). Quando você chama next()um iterador Java sem nenhum elemento restante, a NoSuchElementExceptioné lançada .

Tão eficazmente, você pode fazer uma tentativa ... capturar em Java como a tentativa ... exceto em Python. E sim, de acordo com uma resposta anterior, a filosofia é muito importante no mundo pitonico.

yati sagade
fonte