¿Cómo elegir un solo elemento de un generador (en Python)?

213

Tengo una función de generador como la siguiente:

def myfunct():
  ...
  yield result

La forma habitual de llamar a esta función sería:

for r in myfunct():
  dostuff(r)

Mi pregunta, ¿hay alguna manera de obtener solo un elemento del generador cuando lo desee? Por ejemplo, me gustaría hacer algo como:

while True:
  ...
  if something:
      my_element = pick_just_one_element(myfunct())
      dostuff(my_element)
  ...
Alexandros
fuente

Respuestas:

304

Crea un generador usando

g = myfunct()

Cada vez que desee un artículo, use

next(g)

(o g.next()en Python 2.5 o inferior).

Si el generador sale, subirá StopIteration. Puede capturar esta excepción si es necesario, o usar el defaultargumento para next():

next(g, default_value)
Sven Marnach
fuente
44
Tenga en cuenta que solo aumentará StopIteration cuando intente usar g.next () después de que se haya proporcionado el último elemento en g.
Wilduck
26
next(gen, default)También se puede utilizar para evitar la StopIterationexcepción. Por ejemplo, next(g, None)para un generador de cadenas producirá una cadena o Ninguna después de que finalice la iteración.
Atila el
8
en Python 3000, next () es __next __ ()
Jonathan Baldwin
27
@ JonathanBaldwin: Tu comentario es algo engañoso. En Python 3, se utilizaría la segunda sintaxis dada en mi respuesta, next(g). Esto llamará internamente g.__next__(), pero realmente no tiene que preocuparse por eso, así como generalmente no le importa que len(a)las llamadas internas a.__len__().
Sven Marnach
14
Debería haber sido más claro. g.next()está g.__next__()en py3k. El builtin next(iterator)ha existido desde Python 2.6, y es lo que se debe usar en todos los nuevos códigos de Python, y es trivial para la implementación posterior si necesita admitir py <= 2.5.
Jonathan Baldwin
29

Para elegir solo un elemento de un generador, use breaken una fordeclaración, olist(itertools.islice(gen, 1))

Según su ejemplo (literalmente) puede hacer algo como:

while True:
  ...
  if something:
      for my_element in myfunct():
          dostuff(my_element)
          break
      else:
          do_generator_empty()

Si desea " obtener solo un elemento del generador [una vez generado] cuando quiera " (supongo que el 50% es la intención original y la intención más común) entonces:

gen = myfunct()
while True:
  ...
  if something:
      for my_element in gen:
          dostuff(my_element)
          break
      else:
          do_generator_empty()

De esta manera, generator.next()se puede evitar el uso explícito de , y el manejo de fin de entrada no requiere (críptico)StopIteration manejo de excepciones o comparaciones de valores predeterminados adicionales.

La sección else:de fordeclaración solo es necesaria si desea hacer algo especial en caso de fin de generador.

Nota sobre next()/ .next():

En Python3 .next()se cambió el nombre del método .__next__()por una buena razón: se considera de bajo nivel (PEP 3114). Antes de Python 2.6, la función integrada next()no existía. E incluso se discutió la posibilidad de pasar next()al operatormódulo (lo que habría sido prudente), debido a su rara necesidad y la cuestionable inflación de los nombres incorporados.

Usar next()sin valor predeterminado sigue siendo una práctica de muy bajo nivel: lanzar el críptico StopIterationcomo un rayo de la nada en el código de aplicación normal abiertamente. Y usar next()con centinela predeterminado, que mejor debería ser la única opción para un next()directamente enbuiltins , es limitado y a menudo da razón a una lógica / legibilidad no pitónica extraña.

En pocas palabras: Usar next () debería ser muy raro, como usar funciones del operatormódulo. El uso de for x in iterator, islice, list(iterator)y otras funciones de aceptar un iterador sin problemas es la forma natural de usar iteradores de nivel de aplicación - y bastante siempre es posible. next()es de bajo nivel, un concepto adicional, no obvio, como lo muestra la pregunta de este hilo. Mientras que, por ejemplo, el uso breaken fores convencional.

kxr
fuente
8
Esto es demasiado trabajo para obtener el primer elemento del resultado de una lista. A menudo, no necesito que sea vago, pero no tengo otra opción en py3. ¿No hay algo parecido mySeq.head?
javadba
2

No creo que haya una manera conveniente de recuperar un valor arbitrario de un generador. El generador proporcionará un método next () para atravesarse, pero la secuencia completa no se produce inmediatamente para ahorrar memoria. Esa es la diferencia funcional entre un generador y una lista.

gddc
fuente
1

Para aquellos de ustedes que escanean estas respuestas para obtener un ejemplo de trabajo completo para Python3 ... bueno, aquí van:

def numgen():
    x = 1000
    while True:
        x += 1
        yield x

nums = numgen() # because it must be the _same_ generator

for n in range(3):
    numnext = next(nums)
    print(numnext)

Esto produce:

1001
1002
1003
Dave Rove
fuente
1

El generador es una función que produce un iterador. Por lo tanto, una vez que tenga una instancia de iterador, use next () para obtener el siguiente elemento del iterador. Como ejemplo, use la función next () para obtener el primer elemento, y luego use for inpara procesar los elementos restantes:

# create new instance of iterator by calling a generator function
items = generator_function()

# fetch and print first item
first = next(items)
print('first item:', first)

# process remaining items:
for item in items:
    print('next item:', item)
andrii
fuente
0
generator = myfunct()
while True:
   my_element = generator.next()

asegúrese de atrapar la excepción lanzada después de tomar el último elemento

Juan
fuente
No es válido para Python 3, vea la excelente respuesta de kxr .
clacke
2
Simplemente reemplace "generator.next ()" con "next (generator)" para Python 3.
iyop45
-3

Creo que la única forma es obtener una lista del iterador y luego obtener el elemento que desea de esa lista.

l = list(myfunct())
l[4]
keegan3d
fuente
La respuesta de Sven probablemente sea mejor, pero dejaré esto aquí en caso de que esté más en línea con sus necesidades.
keegan3d
26
Asegúrese de tener un generador finito antes de hacer esto.
Seth
66
Lo sentimos, esto tiene complejidad en la longitud del iterador, mientras que el problema es obviamente O (1).
yo '
1
¡Perder demasiada memoria y proceso para succionar del generador! Además, como @Seth mencionó anteriormente, los generadores no están garantizados para cuándo dejar de generar.
pylover
Obviamente, esta no es la única forma (o la mejor manera si myfunct()genera una gran cantidad de valores) ya que puede usar la función integrada nextpara obtener el siguiente valor generado.
Hola