Comment vérifier si un objet est un objet générateur en python?


157

En python, comment vérifier si un objet est un objet générateur?

Essayer ça -

>>> type(myobject, generator)

donne l'erreur -

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

(Je sais que je peux vérifier si l'objet a une nextméthode pour qu'il soit un générateur, mais je veux un moyen qui me permette de déterminer le type de n'importe quel objet, pas seulement des générateurs.)


4
Quel problème réel essayez-vous de résoudre? Publiez plus de contexte, il y a peut-être un moyen plus intelligent. Pourquoi avez-vous besoin de savoir s'il s'agit d'un générateur?
Daenyth

7
from types import GeneratorType;type(myobject, GeneratorType)vous donnera le résultat approprié pour les objets de la classe «générateur». Mais comme Daenyth l'implique, ce n'est pas nécessairement la bonne voie à suivre.
JAB

7
Si vous recherchez __next__, vous acceptez en fait n'importe quel itérateur, pas seulement les générateurs - ce qui est très probablement ce que vous voulez.

2
Le plus souvent, le vrai point de savoir si quelque chose est un générateur est de pouvoir les éviter, en raison du désir d'itérer plusieurs fois sur la même collection.
Ian

2
Pour les personnes qui s'interrogent sur le cas d'utilisation, cela peut être utile lorsque vous avez besoin de savoir si l'itérateur sera consommé (par exemple, si votre fonction accepte un itérateur mais a besoin d'itérer plus d'une fois, vous voudrez le matérialiser avant de l'itérer)
wbadart

Réponses:


227

Vous pouvez utiliser GeneratorType à partir des types:

>>> import types
>>> types.GeneratorType
<class 'generator'>
>>> gen = (i for i in range(10))
>>> isinstance(gen, types.GeneratorType)
True

5
Cela ne fonctionne malheureusement pas pour les classes de générateur (par exemple, les objets de carte ou de filtre).
Ricardo Cruz

Peut isinstance(gen, (types.GeneratorType, map, filter))- être est-il utile de détecter également mapet filter. Cependant, cela n'inclura toujours pas les autres itérables et itérateurs.
jlh

38

Vous voulez dire des fonctions de générateur? utiliser inspect.isgeneratorfunction.

ÉDITER :

si vous voulez un objet générateur, vous pouvez utiliser inspect.isgenerator comme indiqué par JAB dans son commentaire.


1
la fonction du générateur n'est pas un objet générateur; voir la réponse de @ utdemir
Piotr Findeisen

5
@Piotr: Dans ce cas, vous utilisez inspect.isgenerator.
JAB

@JAB, @Piotr: réfléchi pour aborder toutes les possibilités de ce que l'OP peut signifier, merci JAB :)
mouad

1
Remarque: si vous avez besoin de ce test, vous pouvez éviter une petite surcharge en utilisant @utdemir solution , car inspect.isgeneratorseulement un raccourci à: isinstance(object, types.GeneratorType).
bufh

Voir la réponse @RobertLujo pour la distinction entre l'objet générateur et la fonction générateur. stackoverflow.com/a/32380774/3595112
industryworker3595112

24

Je pense qu'il est important de faire la distinction entre les fonctions du générateur et les générateurs (résultat de la fonction du générateur):

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

appeler generator_function ne donnera pas de résultat normal, il n'exécutera même pas de code dans la fonction elle-même, le résultat sera un objet spécial appelé générateur :

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

donc ce n'est pas une fonction de générateur, mais un générateur:

>>> inspect.isgeneratorfunction(generator)
False

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

et la fonction de générateur n'est pas générateur:

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

juste pour une référence, l'appel réel du corps de la fonction se produira en consommant un générateur, par exemple:

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

Voir aussi En python, existe-t-il un moyen de vérifier si une fonction est une "fonction génératrice" avant de l'appeler?


11

La inspect.isgeneratorfonction est très bien si vous voulez vérifier des générateurs purs (c'est-à-dire des objets de la classe "générateur"). Cependant, il reviendra Falsesi vous cochez, par exemple, un izipitérable. Une autre façon de rechercher un générateur généralisé est d'utiliser cette fonction:

def isgenerator(iterable):
    return hasattr(iterable,'__iter__') and not hasattr(iterable,'__len__')

1
Hmm. Cela retourne vrai pour x=iter([1,2]). Il me semble que c'est vraiment de tester si un objet est un itérateur , pas un générateur. Mais peut-être que "itérateur" est exactement ce que vous entendez par "générateur généralisé".
Josh O'Brien

3

Vous pouvez utiliser l'itérateur ou plus précisément le générateur à partir du module de saisie .

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

résultat:

<class 'generator'>
True
True

1
+1 pour une solution de travail. Ceci étant dit, la documentation de la typing.TypeVarclasse semble décourager l'utilisation de isinstanceen conjonction avec le typingmodule: "Au moment de l'exécution, isinstance(x, T)augmentera TypeError. En général, isinstance()et issubclass()ne doit pas être utilisé avec des types."
Jasha

2
>>> import inspect
>>> 
>>> def foo():
...   yield 'foo'
... 
>>> print inspect.isgeneratorfunction(foo)
True

Cela ne fonctionne que s'il s'agit d'une fonction. Si «foo» est un objet générateur, il affiche «False». Voir ma question, je veux faire des vérifications pour les objets générateurs.
Pushpak Dagade

2

Je sais que je peux vérifier si l'objet a une méthode suivante pour qu'il soit un générateur, mais je veux un moyen qui me permette de déterminer le type de n'importe quel objet, pas seulement des générateurs.

Ne fais pas ça. C'est simplement une très, très mauvaise idée.

À la place, faites ceci:

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

Dans le cas peu probable où le corps de la boucle for aurait également TypeErrors, il y a plusieurs choix: (1) définir une fonction pour limiter la portée des erreurs, ou (2) utiliser un bloc try imbriqué .

Ou (3) quelque chose comme ça pour distinguer tous ces TypeErrors qui flottent.

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`

Ou (4) corrigez les autres parties de votre application pour fournir des générateurs de manière appropriée. C'est souvent plus simple que tout cela.


1
Votre solution capturera les TypeErrors lancés par le corps de la boucle for. J'ai proposé une modification qui empêcherait ce comportement indésirable.
Dunes

C'est la manière la plus pythonique de le faire, si je ne me trompe pas.
JAB

Bien que, si vous itérez sur une liste d'éléments et que plus d'entre eux ne sont pas des itérateurs que des itérateurs, cela pourrait sûrement prendre plus de temps?
Jakob Bowyer

1
@Jakob Bowyer: Les exceptions sont plus rapides que les ifdéclarations. Et. Ce genre de micro-optimisation est une perte de temps. Corrigez l'algorithme qui produit un ensemble d'itérateurs et de non-itérateurs pour ne produire que des itérateurs et vous épargner toute cette douleur.
S.Lott

10
Cela supposerait à tort tout itérable en tant que générateur.
balki

1

Si vous utilisez le serveur Web tornado ou similaire, vous avez peut-être constaté que les méthodes serveur sont en fait des générateurs et non des méthodes. Cela rend difficile l'appel d'autres méthodes car yield ne fonctionne pas à l'intérieur de la méthode et vous devez donc commencer à gérer des pools d'objets générateurs chaînés. Une méthode simple pour gérer des pools de générateurs chaînés consiste à créer une fonction d'aide telle que

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

Écrivant maintenant des générateurs chaînés tels que

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

Produit une sortie

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

C'est probablement ce que vous voulez si vous cherchez à utiliser des générateurs comme alternative au thread ou similaire.


1

(Je sais que c'est un ancien post.) Il n'est pas nécessaire d'importer un module, vous pouvez déclarer un objet pour comparaison au début du programme:

gentyp= type(1 for i in "")                                                                                          
       ...
type(myobject) == gentyp
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.