Pourquoi les itérateurs de Python déclenchent-ils une exception?

48

Voici la syntaxe pour les itérateurs en Java (syntaxe quelque peu similaire en C #):

Iterator it = sequence.iterator();

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

Ce qui a du sens. Voici la syntaxe équivalente en Python:

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

Je pensais que les exceptions n'étaient censées être utilisées que dans des circonstances exceptionnelles.

Pourquoi Python utilise-t-il des exceptions pour arrêter les itérations?

NullUserException
la source

Réponses:

50

Il existe un moyen très pythonique d'écrire cette expression sans écrire explicitement un bloc try-except pour 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)

Vous pouvez en savoir plus sur les PEP 234 255 pertinents si vous souhaitez en savoir plus sur la raison de l’ StopIterationintroduction de cette stratégie et sur la logique qui sous-tend les itérateurs.

Un principe général en python est d’avoir un moyen de faire quelque chose (voir import this), et de préférence son beau, explicite, lisible et simple, que la méthode pythonique satisfait. Votre code équivalent est uniquement nécessaire car python ne donne pas aux itérateurs une hasNextfonction membre; préférant que les utilisateurs fassent simplement une lecture directe des itérateurs (et si vous avez besoin de faire autre chose, essayez simplement de le lire et d’obtenir une exception).

Cette interception automatique d'une StopIterationexception à la fin d'un itérateur a du sens et est analogue au EOFErrorlevé si vous lisez au-delà de la fin du fichier.

dr jimbob
la source
6
La méthode "Pythonic" semble bien plus ressembler à "pour valeur en séquence:" plutôt que "pour valeur en iter (séquence):" .. Update post?
Yam Marcovic
15
@Yam: Je suis d'accord. Ce n'est pas pythonique de prendre une séquence existante et de la convertir en itérateur, juste pour lui appliquer une boucle for; la séquence est déjà une itérable, la conversion de a listen a listiteratorest donc inutile. J'ai gardé la première ligne que de suivre le point de départ de NullUserException, pour expliquer comment vous devriez boucler sur un itérateur, ce qui est de la même façon que vous devriez boucler sur tout itérable ( list, set, str, tuple, dict, file, generator, etc.). J'aurais pu faire quelque chose comme it = itertools.combinations("ABCDE", 2)obtenir un meilleur exemple d'un itérateur significatif.
dr jimbob
1
it = iter(sequence)n'est pas nécessaire.
Caridorc
2
@Caridorc - Si vous lisez les commentaires, votre question sera traitée. Cela n'est pas nécessaire et a été fait pour suivre le point de départ de la question (où ils demandaient explicitement iterators) et vous devez itergénérer explicitement un iterator(try type([])( list) vs type(iter([]))( listiterator)).
dr jimbob
//drjimbob, vous soulevez un excellent point dans le deuxième commentaire de cette question. Je suis un peu nouveau sur les fonctionnalités avancées de iterables, et je ne l'aurais pas compris si je n'avais pas lu les commentaires. Je pense que beaucoup d’entre nous, pauvres âmes autodidactes, bénéficieraient si nous pouvions comprendre la question de savoir comment la question elle-même pourrait être changée en "La façon Python" comme première partie de la réponse.
Nathan Basanese
28

La raison pour laquelle python utilise une exception pour arrêter une itération est documentée dans PEP 234 :

On s'est demandé si une exception signalant la fin de l'itération n'était pas trop chère. Plusieurs alternatives pour l'exception StopIteration ont été proposées: une valeur spéciale End pour signaler la fin, une fonction end () pour tester si l'itérateur est terminé, même en réutilisant l'exception IndexError.

  • Une valeur spéciale pose le problème suivant: si une séquence contient jamais cette valeur spéciale, une boucle sur cette séquence se termine prématurément sans aucun avertissement. Si l'expérience des chaînes de caractères C à terminaison nulle ne nous a pas appris les problèmes que cela peut causer, imaginez le problème qu'un outil d'introspection Python aurait à parcourir sur une liste de tous les noms intégrés, en supposant que la valeur End spéciale était une valeur construite. en nom!

  • L'appel d'une fonction end () nécessiterait deux appels par itération. Deux appels coûtent beaucoup plus cher qu'un appel, plus un test pour une exception. En particulier, le temps critique pour la boucle peut être testé à très bon marché pour une exception.

  • Réutiliser IndexError peut prêter à confusion car il peut s’agir d’une véritable erreur, qui serait masquée en mettant fin à la boucle prématurément.

Remarque: la méthode idiomatique utilisée pour boucler une séquence est la suivante:

for value in sequence:
    print (value)
lesmana
la source
20

C'est une différence de philosophie. La philosophie de conception Pythonic est l’ EAFP :

Plus facile de demander pardon que de permission. Ce style de codage Python commun suppose l'existence de clés ou d'attributs valides et intercepte des exceptions si l'hypothèse s'avère fausse. Ce style propre et rapide se caractérise par la présence d' un grand nombre tryet exceptdéclarations. La technique contraste avec le style LBYL commun à beaucoup d'autres langages tels que le C ...

Charles E. Grant
la source
7

C'est juste que l'implémentation Java a une hasNext()méthode pour que vous puissiez rechercher un itérateur vide avant d'effectuer une next(). Lorsque vous appelez next()un itérateur Java sans aucun élément, un NoSuchElementExceptionest lancé .

Tellement efficacement, vous pouvez faire un try..catch en Java comme le try..except en Python. Et oui, comme dans une réponse précédente, la philosophie est très importante dans le monde pythonique.

Yati Sagade
la source