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

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

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

¿Pero si quito la función de statement de rendimiento, se producirá una 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 un 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 sentencias 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 ?

    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 statement 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 su lugar, 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 statement de return o al final del código de la función del generador sin elevar nada, Python boostá 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 más 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 "", line 1, in  next(g) StopIteration 

    Llamar a simple_generator siempre devuelve un objeto generator inmediatamente (sin ejecutar ninguno de los códigos en la función). Cada llamada de next en el objeto generador ejecuta el código hasta la siguiente statement 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 que la excepción no sea 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 un StopIteration ser StopIteration desde una función del generador. Si eso sucede, Python lo convertirá en una excepción RuntimeError lugar.

    Esto significa que será necesario modificar el código como los ejemplos en itertools que usan una función StopIteration para interrumpir una función del generador. 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.

    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.

    Teniendo en cuenta su función, y un 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. 🙂

    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 la StopIteration . Lo que hace el yield 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 (que recibe un llamado de forma implícita por un bucle for)). Si deja la statement de yield fuera de ella, entonces Python ejecuta todo el bucle while inmediatamente, 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.