¿Por qué los iteradores en Python generan una excepción?

48

Aquí está la sintaxis para los iteradores en Java (sintaxis algo similar en C #):

Iterator it = sequence.iterator();

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

Lo cual tiene sentido. Aquí está la sintaxis equivalente en Python:

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

Pensé que se suponía que las Excepciones debían usarse solo en, bueno, circunstancias excepcionales.

¿Por qué Python usa excepciones para detener la iteración?

NullUserException
fuente

Respuestas:

50

Hay una forma muy pitónica de escribir esa expresión sin escribir explícitamente un bloque try-except para 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)

Puede leer sobre las PEP relevantes 234 255 si desea saber más sobre por qué StopIterationse introdujo y la lógica detrás de los iteradores.

Un principio general en python es tener una forma de hacer algo (ver import this), y preferiblemente es hermoso, explícito, legible y simple, que satisface el método pitónico. Su código equivalente solo es necesario ya que python no le da a los iteradores una hasNextfunción miembro; prefiriendo que las personas simplemente recorran los iteradores directamente (y si necesita hacer algo más, simplemente intente leerlo y detectar una excepción).

Esta captura automática de una StopIterationexcepción al final de un iterador tiene sentido y es un análogo de la EOFErrorprovocada si lee más allá del final del archivo.

dr jimbob
fuente
66
La forma "Pythonic" parece mucho más como "por valor en secuencia:" en lugar de "por valor en iter (secuencia):" .. ¿Actualizar publicación?
Yam Marcovic
15
@Yam: estoy de acuerdo. No es pitónico tomar una secuencia existente y convertirla en un iterador solo para aplicarle un bucle for; la secuencia ya es iterable, por lo que la conversión de lista a no listiteratortiene sentido. Me quedé con la primera línea sólo para seguir el punto de partida de NullUserException, para explicar cómo se debe reproducirse sobre un iterador, que es la misma forma en que debe reproducirse a través de cualquier iterable ( list, set, str, tuple, dict, file, generator, etc.). Podría haber hecho algo como it = itertools.combinations("ABCDE", 2)obtener un mejor ejemplo de un iterador significativo.
dr jimbob
1
it = iter(sequence)no es necesario.
Caridorc
2
@ Caridorc: si lees los comentarios, tu punto se abordará. No es necesario y se hizo para seguir el punto de partida de la pregunta (donde se preguntaba explícitamente iterators) y es necesario itergenerar explícitamente un iterator(try type([])( list) vs type(iter([]))( listiterator)).
dr jimbob
//, @drjimbob, planteas un excelente punto en el segundo comentario a esta pregunta. Soy un poco nuevo en las características avanzadas de los iterables, y no lo habría captado si no hubiera leído los comentarios. Creo que nos beneficiaría a muchos de nosotros, las almas pobres de autoeducación, si pudiéramos ver ese punto sobre cómo la pregunta en sí misma podría cambiarse a "The Python Way" como la primera parte principal de la respuesta.
Nathan Basanese
28

La razón por la cual Python usa una excepción para detener una iteración está documentada en PEP 234 :

Se ha cuestionado si una excepción para señalar el final de la iteración no es demasiado costosa. Se han propuesto varias alternativas para la excepción StopIteration: un valor especial End para señalar el final, una función end () para probar si el iterador ha finalizado, incluso reutilizando la excepción IndexError.

  • Un valor especial tiene el problema de que si una secuencia contiene ese valor especial, un ciclo sobre esa secuencia terminará prematuramente sin ninguna advertencia. Si la experiencia con cadenas C terminadas en nulo no nos ha enseñado los problemas que esto puede causar, imagine el problema que una herramienta de introspección de Python tendría iterando sobre una lista de todos los nombres integrados, suponiendo que el valor final especial fuera un ¡en nombre!

  • Llamar a una función end () requeriría dos llamadas por iteración. Dos llamadas es mucho más costoso que una llamada más una prueba para una excepción. Especialmente el tiempo crítico para el bucle puede probar muy barato para una excepción.

  • Reutilizar IndexError puede causar confusión porque puede ser un error genuino, que se enmascararía al finalizar el ciclo prematuramente.

Nota: la forma idiomática de Python de recorrer una secuencia es así:

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

Es una diferencia en filosofía. La filosofía de diseño Pythonic es EAFP :

Es más fácil pedir perdón que permiso. Este estilo de codificación Python común supone la existencia de claves o atributos válidos y captura excepciones si el supuesto resulta falso. Este estilo limpio y rápido se caracteriza por la presencia de muchos tryy exceptdeclaraciones. La técnica contrasta con el estilo LBYL común a muchos otros lenguajes como C ...

Charles E. Grant
fuente
7

Es solo que la implementación de Java tiene un hasNext()método para que pueda verificar si hay un iterador vacío antes de realizar un next(). Cuando llama next()a un iterador Java sin elementos restantes, NoSuchElementExceptionse emite un .

Así que efectivamente, puedes hacer un try..catch en Java como try..except en Python. Y sí, según una respuesta anterior, la filosofía es muy importante en el mundo pitónico.

Yati Sagade
fuente