¿Cómo verificar si un objeto es un objeto generador en Python?

157

En python, ¿cómo verifico si un objeto es un objeto generador?

Intentando esto

>>> type(myobject, generator)

da el error

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'generator' is not defined

(Sé que puedo verificar si el objeto tiene un nextmétodo para que sea un generador, pero quiero usar algún método para determinar el tipo de cualquier objeto, no solo los generadores).

Pushpak Dagade
fuente
44
¿Qué problema real estás tratando de resolver? Publicar más contexto, puede haber una forma más inteligente. ¿Por qué necesitas saber si es un generador?
Daenyth
77
from types import GeneratorType;type(myobject, GeneratorType)le dará el resultado adecuado para los objetos de la clase 'generador'. Pero como Daenyth implica, ese no es necesariamente el camino correcto.
JAB
77
Si está buscando __next__, en realidad está aceptando cualquier iterador, no solo generadores, que es muy probable que sea lo que desea.
2
En la mayoría de los casos, el punto real de saber si algo es un generador es poder evitarlos, debido al deseo de iterar sobre la misma colección varias veces.
Ian
2
Para las personas que se preguntan sobre el caso de uso, esto podría ser útil cuando necesite saber si se consumirá el iterador (por ejemplo, si su función acepta algún iterador pero necesita iterar más de una vez, querrá materializarlo antes de iterar)
wbadart

Respuestas:

227

Puede usar GeneratorType de tipos:

>>> import types
>>> types.GeneratorType
<class 'generator'>
>>> gen = (i for i in range(10))
>>> isinstance(gen, types.GeneratorType)
True
utdemir
fuente
55
Desafortunadamente, esto no funciona para las clases generadoras (por ejemplo, objetos de mapa o filtro).
Ricardo Cruz
Quizás isinstance(gen, (types.GeneratorType, map, filter))sea ​​útil también detectar mapy filter. Sin embargo, esto todavía no incluirá otros iterables e iteradores.
Jlh
38

¿Te refieres a las funciones del generador? uso inspect.isgeneratorfunction.

EDITAR:

si desea un objeto generador, puede usar inspect.isgenerator como lo señala JAB en su comentario.

mouad
fuente
1
la función del generador no es un objeto generador; ver la respuesta de @ utdemir
Piotr Findeisen
55
@Piotr: en cuyo caso lo usa inspect.isgenerator.
JAB
@JAB, @Piotr: Reflejado para abordar todas las posibilidades de lo que puede significar el OP, gracias JAB :)
mouad
1
Nota: si sólo se necesita esta prueba, puede evitar una pequeña sobrecarga mediante el uso de @utdemir solución , porque inspect.isgeneratorsólo es una abreviatura para: isinstance(object, types.GeneratorType).
bufh
Vea la respuesta @RobertLujo para la distinción entre objeto generador y función generador. stackoverflow.com/a/32380774/3595112
industryworker3595112
24

Creo que es importante hacer una distinción entre las funciones del generador y los generadores (resultado de la función del generador):

>>> def generator_function():
...     yield 1
...     yield 2
...
>>> import inspect
>>> inspect.isgeneratorfunction(generator_function)
True

llamar a generator_function no producirá un resultado normal, incluso no ejecutará ningún código en la función en sí, el resultado será un objeto especial llamado generador :

>>> generator = generator_function()
>>> generator
<generator object generator_function at 0x10b3f2b90>

entonces no es función del generador, sino generador:

>>> inspect.isgeneratorfunction(generator)
False

>>> import types
>>> isinstance(generator, types.GeneratorType)
True

y la función del generador no es generador:

>>> isinstance(generator_function, types.GeneratorType)
False

solo como referencia, la llamada real del cuerpo de la función ocurrirá al consumir el generador, por ejemplo:

>>> list(generator)
[1, 2]

Ver también En Python, ¿hay alguna manera de verificar si una función es una "función generadora" antes de llamarla?

Robert Lujo
fuente
11

La inspect.isgeneratorfunción está bien si desea buscar generadores puros (es decir, objetos de la clase "generador"). Sin embargo, volverá Falsesi marca, por ejemplo, un izipiterable. Una forma alternativa de verificar un generador generalizado es usar esta función:

def isgenerator(iterable):
    return hasattr(iterable,'__iter__') and not hasattr(iterable,'__len__')
Luca Sbardella
fuente
1
Hmm Esto devuelve cierto para x=iter([1,2]). Me parece que realmente está probando si un objeto es o no un iterador , no un generador. Pero tal vez "iterador" es exactamente lo que quiere decir con "generador generalizado".
Josh O'Brien
3

Puede usar el iterador o más específicamente, el generador del módulo de escritura .

from typing import Generator, Iterator
g = (i for i in range(1_000_000))
print(type(g))
print(isinstance(g, Generator))
print(isinstance(g, Iterator))

resultado:

<class 'generator'>
True
True
usuario9074332
fuente
1
+1 para una solución de trabajo. Dicho esto, los documentos para la typing.TypeVarclase parecen desalentar el uso isinstanceen conjunto con el typingmódulo: "En tiempo de ejecución, isinstance(x, T)aumentará TypeError. En general, isinstance()y issubclass()no debe usarse con tipos".
Jasha
2
>>> import inspect
>>> 
>>> def foo():
...   yield 'foo'
... 
>>> print inspect.isgeneratorfunction(foo)
True
Corey Goldberg
fuente
Esto funciona solo si es una función. Si 'foo' es un objeto generador, muestra 'False'. Vea mi pregunta, quiero hacer comprobaciones para objetos generadores.
Pushpak Dagade
2

Sé que puedo verificar si el objeto tiene un próximo método para que sea un generador, pero quiero usar alguna forma para determinar el tipo de cualquier objeto, no solo los generadores.

No hagas esto. Es simplemente una muy, muy mala idea.

En cambio, haz esto:

try:
    # Attempt to see if you have an iterable object.
    for i in some_thing_which_may_be_a_generator:
        # The real work on `i`
except TypeError:
     # some_thing_which_may_be_a_generator isn't actually a generator
     # do something else

En el improbable caso de que el cuerpo del bucle for también tenga TypeErrors, hay varias opciones: (1) definir una función para limitar el alcance de los errores, o (2) usar un try anidado bloque .

O (3) algo como esto para distinguir todos estos TypeErrors que están flotando.

try:
    # Attempt to see if you have an iterable object.
    # In the case of a generator or iterator iter simply 
    # returns the value it was passed.
    iterator = iter(some_thing_which_may_be_a_generator)
except TypeError:
     # some_thing_which_may_be_a_generator isn't actually a generator
     # do something else
else:
    for i in iterator:
         # the real work on `i`

O (4) arregle las otras partes de su aplicación para proporcionar generadores adecuadamente. Eso es a menudo más simple que todo esto.

S.Lott
fuente
1
Su solución detectará TypeErrors lanzados por el cuerpo del bucle for. He propuesto una edición que evitaría este comportamiento indeseable.
Dunas
Esta es la forma más pitónica de hacerlo, si no me equivoco.
JAB
Sin embargo, si está iterando sobre una lista de elementos y más de ellos no son iteradores que son iteradores, ¿entonces esto podría tomar más tiempo?
Jakob Bowyer
1
@Jakob Bowyer: las excepciones son más rápidas que las ifdeclaraciones. Y. Ese tipo de micro-optimización es una pérdida de tiempo. Arregle el algoritmo que produce una bolsa mixta de iteradores y no iteradores para producir solo iteradores y ahorrarse todo este dolor.
S.Lott
10
Esto supondría erróneamente cualquier iterable como generador.
balki
1

Si está utilizando un servidor web tornado o similar, es posible que haya encontrado que los métodos del servidor son en realidad generadores y no métodos. Esto hace que sea difícil llamar a otros métodos porque el rendimiento no funciona dentro del método y, por lo tanto, debe comenzar a administrar grupos de objetos generadores encadenados. Un método simple para administrar grupos de generadores encadenados es crear una función de ayuda como

def chainPool(*arg):
    for f in arg:
      if(hasattr(f,"__iter__")):
          for e in f:
             yield e
      else:
         yield f

Ahora escribiendo generadores encadenados como

[x for x in chainPool(chainPool(1,2),3,4,chainPool(5,chainPool(6)))]

Produce salida

[1, 2, 3, 4, 5, 6]

Que es probablemente lo que desea si está buscando utilizar generadores como una alternativa de hilo o similar.

usuario6830669
fuente
1

(Sé que es una publicación anterior). No es necesario importar un módulo, puede declarar un objeto para comparar al comienzo del programa:

gentyp= type(1 for i in "")                                                                                          
       ...
type(myobject) == gentyp
kantal
fuente