¿Cómo elegir solo un artículo de un generador?

2 minutos de lectura

avatar de usuario
Alejandro

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)
  ...

avatar de usuario
Sven Marnach

Crea un generador usando

g = myfunct()

Cada vez que desee un artículo, utilice

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 default argumento a next():

next(g, default_value)

  • Tenga en cuenta que solo generará StopIteration cuando intente usar g.next() después de que se haya proporcionado el último elemento en g.

    – pato salvaje

    19 de enero de 2011 a las 22:11

  • next(gen, default) también se puede utilizar para evitar la StopIteration excepción. Por ejemplo next(g, None) para un generador de cadenas producirá una cadena o Ninguno después de que finalice la iteración.

    – Atila

    6 de marzo de 2013 a las 14:18

  • en Python 3000, next() es __next__()

    –Jonathan Baldwin

    10 de abril de 2014 a las 2:52

  • @JonathanBaldwin: Su comentario es algo engañoso. En Python 3, usaría la segunda sintaxis dada en mi respuesta, next(g). Esto llamará internamente g.__next__()pero en realidad no tienes que preocuparte por eso, al igual que normalmente no te importa eso len(a) llamadas internas a.__len__().

    – Sven Marnach

    10 de abril de 2014 a las 10:31

  • Debería haber sido más claro. g.next() es g.__next__() en py3k. el incorporado next(iterator) ha existido desde Python 2.6, y es lo que debe usarse en todo el nuevo código de Python, y es trivial volver a implementarlo si necesita admitir py

    –Jonathan Baldwin

    10/04/2014 a las 19:58


avatar de usuario
kxr

Para elegir solo un elemento de un uso generador break en un for declaración, o list(itertools.islice(gen, 1))

De acuerdo con 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 tu quieres “obtener sólo un elemento de la [once generated] generador cuando yo 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 el uso explícito de generator.next() se puede evitar, y el manejo de fin de entrada no requiere (críptico) StopIteration manejo de excepciones o comparaciones de valores predeterminados adicionales.

los else: de for La sección de declaración solo es necesaria si desea hacer algo especial en caso de final del generador.

Nota sobre next() / .next():

En Python3 el .next() el método fue renombrado a .__next__() por una buena razón: se considera de bajo nivel (PEP 3114). Antes de Python 2.6, la función incorporada next() no existió. Y hasta se discutió mudarse next() hacia operator módulo (que habría sido inteligente), debido a su rara necesidad y cuestionable inflación de nombres incorporados.

Usando next() sin defecto sigue siendo una práctica de muy bajo nivel: lanzar el críptico StopIteration como un rayo de la nada en el código de aplicación normal abiertamente. y usando next() con centinela predeterminado, que debería ser la única opción para un next() directamente en builtins – es limitado y, a menudo, da motivos para una extraña lógica/legibilidad no pitónica.

En pocas palabras: usar next() debería ser muy raro, como usar funciones de operator módulo. Usando for x in iterator , islice, list(iterator) y otras funciones que aceptan un iterador sin problemas es la forma natural de usar iteradores en el nivel de aplicación, y siempre es posible. next() es de bajo nivel, un concepto adicional, no obvio, como muestra la pregunta de este hilo. Mientras que, por ejemplo, usando break en for es convencional.

  • Esto es demasiado trabajo para solo obtener el primer elemento del resultado de una lista. A menudo, no necesito que sea perezoso, pero no tengo otra opción en py3. ¿No hay algo parecido a mySeq.head ?

    – Proyectos de la costa oeste

    3 de marzo de 2018 a las 15:57

avatar de usuario
andrii

Generator es una función que produce un iterador. Por lo tanto, una vez que tenga una instancia de iterador, use Siguiente() para obtener el siguiente elemento del iterador. Como ejemplo, use la función next() para obtener el primer elemento y luego use for in para 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)

avatar de usuario
marca mcdonald

Puede elegir elementos específicos mediante la desestructuración, por ejemplo:

>>> first, *middle, last = range(10)
>>> first
0
>>> middle
[1, 2, 3, 4, 5, 6, 7, 8]
>>> last
9

Tenga en cuenta que esto consumirá su generador, por lo que, si bien es muy legible, es menos eficiente que algo como next()y ruinoso en infinitos generadores:

>>> first, *rest = itertools.count()
🔥🔥🔥

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

avatar de usuario
John

generator = myfunct()
while True:
   my_element = generator.next()

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

avatar de usuario
dave rove

Para aquellos de ustedes que buscan en estas respuestas un ejemplo de trabajo completo para Python3… bueno, aquí tienen:

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 da como resultado:

1001
1002
1003

¿Ha sido útil esta solución?