used - next() no juega bien con ninguno/todos en python
python generator to list (2)
El problema no está en utilizar all
, es que tiene una expresión generadora como parámetro para all
. El StopIteration
se propaga a la expresión del generador, que en realidad no sabe dónde se originó, por lo que hace lo habitual y termina la iteración.
Puede ver esto reemplazando su función de error
con algo que provoca el error directamente:
def error2(): raise StopIteration
>>> all(error2() for i in range(2))
True
La última pieza del rompecabezas es saber qué hace all
con una secuencia vacía:
>>> all([])
True
Si va a usar el next
directamente, debe estar preparado para capturar StopIteration
usted mismo.
Edición: Es bueno ver que los desarrolladores de Python consideran esto como un error y están tomando medidas para cambiarlo en 3.7.
Corrí un error hoy que surgió porque estaba usando next()
para extraer un valor, y ''no encontrado'' emite una StopIteration
.
Normalmente, eso detendría el programa, pero la función que usaba a next
se llamaba dentro de una iteración all()
, por lo que all
acabó antes y devolvió True
.
¿Es este un comportamiento esperado? ¿Hay guías de estilo que ayuden a evitar este tipo de cosas?
Ejemplo simplificado:
def error(): return next(i for i in range(3) if i==10)
error() # fails with StopIteration
all(error() for i in range(2)) # returns True
Si bien este es el comportamiento predeterminado en las versiones de Python hasta 3.6 inclusive, se considera que es un error en el idioma y está programado para cambiar en Python 3.7 para que se genere una excepción.
Como dice PEP 479 :
La interacción de los generadores y
StopIteration
es actualmente algo sorprendente y puede ocultar errores ocultos. Una excepción inesperada no debería dar como resultado un comportamiento alterado sutilmente, sino que debería causar un rastreo ruidoso y fácilmente depurado. Actualmente,StopIteration
accidentalmente dentro de una función del generador se interpretará como el final de la iteración por la construcción de bucle que impulsa el generador.
Desde Python 3.5 en adelante, es posible cambiar el comportamiento predeterminado al programado para 3.7. Este codigo
# gs_exc.py
from __future__ import generator_stop
def error():
return next(i for i in range(3) if i==10)
all(error() for i in range(2))
... plantea la siguiente excepción:
Traceback (most recent call last):
File "gs_exc.py", line 8, in <genexpr>
all(error() for i in range(2))
File "gs_exc.py", line 6, in error
return next(i for i in range(3) if i==10)
StopIteration
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "gs_exc.py", line 8, in <module>
all(error() for i in range(2))
RuntimeError: generator raised StopIteration
En Python 3.5 y 3.6 sin la importación de __future__
, __future__
una advertencia. Por ejemplo:
# gs_warn.py
def error():
return next(i for i in range(3) if i==10)
all(error() for i in range(2))
$ python3.5 -Wd gs_warn.py
gs_warn.py:6: PendingDeprecationWarning: generator ''<genexpr>'' raised StopIteration
all(error() for i in range(2))
$ python3.6 -Wd gs_warn.py
gs_warn.py:6: DeprecationWarning: generator ''<genexpr>'' raised StopIteration
all(error() for i in range(2))