when the occurs method iter has for following error accessed python yield stopiteration

python - the - ¿Cómo se captura la excepción StopIteration de rendimiento?



python__ iter__ method (4)

Por qué en el ejemplo termina la función:

def func(iterable): while True: val = next(iterable) yield val

¿Pero si desactivo la función de declaración de rendimiento, se generará la excepción StopIteration?

EDITAR: Lo siento por engañar a los chicos. Sé qué son los generadores y cómo usarlos. Por supuesto, cuando dije que la función terminaba, no me refería a una evaluación ansiosa de la función. Solo implicé que cuando uso la función para producir el generador:

gen = func(iterable)

en caso de func funciona y devuelve el mismo generador, pero en caso de func2:

def func2(iterable): while True: val = next(iterable)

eleva StopIteration en lugar de Ninguno retorno o bucle infinito.

Déjame ser más específico. Hay una función de tee en itertools que es equivalente a:

def tee(iterable, n=2): it = iter(iterable) deques = [collections.deque() for i in range(n)] def gen(mydeque): while True: if not mydeque: # when the local deque is empty newval = next(it) # fetch a new value and for d in deques: # load it to all the deques d.append(newval) yield mydeque.popleft() return tuple(gen(d) for d in deques)

Hay, de hecho, algo de magia, porque la función anidada gen tiene un bucle infinito sin declaraciones de ruptura. La función gen finaliza debido a una excepción StopIteration cuando no hay elementos en ella . Pero termina correctamente (sin generar excepciones), es decir, simplemente detiene el bucle. Entonces la pregunta es : ¿dónde se maneja StopIteration ?


Cuando una función contiene yield , la llamada no ejecuta realmente nada, simplemente crea un objeto generador. Solo la iteración sobre este objeto ejecutará el código. Entonces, supongo que solo estás llamando a la función, lo que significa que la función no StopIteration porque nunca se está ejecutando.

Dada tu función, y una iterable:

def func(iterable): while True: val = next(iterable) yield val iterable = iter([1, 2, 3])

Esta es la forma incorrecta de llamarlo:

func(iterable)

Esta es la manera correcta:

for item in func(iterable): # do something with item

También puede almacenar el generador en una variable y llamar a next() en él (o iterar sobre él de alguna otra manera):

gen = func(iterable) print(next(gen)) # prints 1 print(next(gen)) # prints 2 print(next(gen)) # prints 3 print(next(gen)) # StopIteration

Por cierto, una mejor manera de escribir su función es la siguiente:

def func(iterable): for item in iterable: yield item

O en Python 3.3 y posteriores:

def func(iterable): yield from iter(iterable)

Por supuesto, los generadores reales rara vez son tan triviales. :-)


Para responder a su pregunta acerca de dónde se StopIteration el StopIteration en el generador de gen creado dentro de itertools.tee : no lo hace. Corresponde al consumidor de los resultados del tee para capturar la excepción a medida que se repiten.

En primer lugar, es importante tener en cuenta que una función generadora (que es cualquier función con una declaración de yield en ella, en cualquier lugar) es fundamentalmente diferente de una función normal. En lugar de ejecutar el código de la función cuando se le llama, en cambio, solo obtendrá un objeto generator cuando llame a la función. Solo cuando recorra el generador ejecutará el código.

Una función de generador nunca terminará de iterar sin elevar StopIteration (a menos que en su lugar StopIteration alguna otra excepción). StopIteration es la señal del generador de que está hecho y no es opcional. Si llega a una declaración de return o al final del código de la función del generador sin elevar nada, Python aumentará StopIteration por usted.

Esto es diferente de las funciones regulares, que devuelven None si llegan al final sin devolver nada más. Se relaciona con las diferentes formas en que funcionan los generadores, como describí anteriormente.

Aquí hay una función de generador de ejemplo que hará que sea fácil ver cómo se StopIteration :

def simple_generator(): yield "foo" yield "bar" # StopIteration will be raised here automatically

Esto es lo que pasa cuando lo consumes:

>>> g = simple_generator() >>> next(g) ''foo'' >>> next(g) ''bar'' >>> next(g) Traceback (most recent call last): File "<pyshell#6>", line 1, in <module> next(g) StopIteration

Llamar a simple_generator siempre devuelve un objeto generator inmediatamente (sin ejecutar ningún código en la función). Cada llamada de next en el objeto generador ejecuta el código hasta la siguiente declaración de yield y devuelve el valor producido. Si no hay más que obtener, se StopIteration .

Ahora, normalmente no ve excepciones de StopIteration . La razón de esto es que usualmente consumes generadores dentro for bucles. Una instrucción for llamará automáticamente la next una y otra vez hasta que se StopIteration . StopIteration y suprimirá la excepción StopIteration para usted, por lo que no necesita StopIteration con los bloques try / except para lidiar con ella.

Un bucle for como for item in iterable: do_suff(item) es casi exactamente equivalente a este bucle while (la única diferencia es que un real for no necesita una variable temporal para mantener el iterador):

iterator = iter(iterable) try: while True: item = next(iterator) do_stuff(item) except StopIteration: pass finally: del iterator

La función de generador de gen que mostró en la parte superior es una excepción. Utiliza la excepción StopIteration producida por el iterador que consume, ya que es su propia señal de que se ha terminado la iteración. Es decir, en lugar de capturar el StopIteration y luego salir del bucle, simplemente deja la excepción sin ser detectada (presumiblemente para ser atrapada por algún código de nivel superior).

Sin relación con la pregunta principal, hay otra cosa que quiero señalar. En tu código, estás llamando a next a una variable llamada iterable . Si toma ese nombre como documentación para el tipo de objeto que obtendrá, esto no es necesariamente seguro.

next es parte del protocolo del iterator , no el protocolo iterable (o container ). Puede funcionar para algunos tipos de iterables (como archivos y generadores, ya que esos tipos son sus propios iteradores), pero fallará para otros iterables, como tuplas y listas. El enfoque más correcto es invocar iter en su valor iterable , luego llamar al next iterador que recibe. (O simplemente úselo for bucles, que llaman a ambos iter y al next para usted en el momento apropiado)

Edición: acabo de encontrar mi propia respuesta en una búsqueda de Google para una pregunta relacionada, y pensé que me actualizaría para señalar que la respuesta anterior no será completamente cierta en futuras versiones de Python. La PEP 479 está cometiendo un error al permitir que una StopIteration ser StopIteration desde una función de generador. Si eso sucede, Python lo convertirá en una excepción RuntimeError .

Esto significa que el código como los ejemplos en itertools que usan una función StopIteration para interrumpir la función de un generador deberá modificarse. Por lo general, deberá capturar la excepción con un try / except y luego return .

Debido a que este es un cambio incompatible hacia atrás, se está introduciendo gradualmente. En Python 3.5, todo el código funcionará como antes por defecto, pero puede obtener el nuevo comportamiento con from __future__ import generator_stop . En Python 3.6, el código seguirá funcionando, pero dará una advertencia. En Python 3.7, el nuevo comportamiento se aplicará todo el tiempo.


Sin el yield , recorres todo el iterable sin parar de hacer nada con val . El bucle while no captura la excepción StopIteration . Un equivalente for bucle sería:

def func(iterable): for val in iterable: pass

que captura el StopIteration y simplemente sale del bucle y, por lo tanto, regresa de la función.

Puedes capturar explícitamente la excepción:

def func(iterable): while True: try: val = next(iterable) except StopIteration: break


yield no atrapa el StopIteration . Lo que el yield hace para su función es que hace que se convierta en una función generadora en lugar de una función regular. Por lo tanto, el objeto devuelto de la llamada a la función es un objeto iterable (que calcula el siguiente valor cuando se lo pide con la next función (a la que se llama implícitamente mediante un bucle for)). Si deja la declaración de yield fuera de ella, entonces Python ejecuta todo el bucle while de inmediato, lo que termina agotando el iterable (si es finito) y eleva el StopIteration justo cuando lo llama.

considerar:

x = func(x for x in []) next(x) #raises StopIteration

Un bucle for captura la excepción: así es como sabe cuándo dejar de llamar next de la iteración que le dio.