Come verificare se un oggetto è un oggetto generatore in Python?


157

In Python, come posso verificare se un oggetto è un oggetto generatore?

Provando questo -

>>> type(myobject, generator)

dà l'errore -

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

(So ​​che posso verificare se l'oggetto ha un nextmetodo per essere un generatore, ma voglio in qualche modo usare il quale posso determinare il tipo di qualsiasi oggetto, non solo i generatori.)


4
Quale problema reale stai cercando di risolvere? Pubblica più contesto, potrebbe esserci un modo più intelligente. Perché devi sapere se è un generatore?
Daenyth,

7
from types import GeneratorType;type(myobject, GeneratorType)ti darà il risultato corretto per gli oggetti della classe "generatore". Ma come suggerisce Daenyth, questa non è necessariamente la strada giusta da percorrere.
JAB,

7
Se stai verificando __next__, stai effettivamente accettando qualsiasi iteratore, non solo generatori, il che è molto probabilmente quello che vuoi.

2
Il più delle volte, il vero punto di sapere se qualcosa è un generatore è quello di essere in grado di evitarli, a causa del desiderio di ripetere più volte la stessa raccolta.
Ian,

2
Per le persone che si chiedono il caso d'uso, questo potrebbe essere utile quando è necessario sapere se l'iteratore verrà consumato (ad esempio se la funzione accetta qualsiasi iteratore ma deve iterare più di una volta, è necessario materializzarlo prima di iterare)
wbadart,

Risposte:


227

È possibile utilizzare GeneratorType dai tipi:

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

5
Questo purtroppo non funziona per le classi del generatore (ad esempio oggetti mappa o filtro).
Ricardo Cruz,

Forse isinstance(gen, (types.GeneratorType, map, filter))è utile anche per rilevare mape filter. Questo comunque non includerà altri iterabili e iteratori.
jlh

38

Intendi le funzioni del generatore? usare inspect.isgeneratorfunction.

MODIFICARE :

se vuoi un oggetto generatore puoi usare inspect.isgenerator come sottolineato da JAB nel suo commento.


1
la funzione generatore non è un oggetto generatore; vedi la risposta di @utdemir
Piotr Findeisen,

5
@Piotr: nel qual caso utilizzi inspect.isgenerator.
JAB,

@JAB, @Piotr: riflesso per affrontare tutte le possibilità di ciò che l'OP può significare, grazie JAB :)
mouad,

1
Nota: se avete solo bisogno di questo test, è possibile evitare un piccolo overhead utilizzando @utdemir soluzione , perché inspect.isgeneratorè solo una scorciatoia per: isinstance(object, types.GeneratorType).
bufh,

Vedi la risposta di @RobertLujo per la distinzione tra oggetto generatore e funzione generatore. stackoverflow.com/a/32380774/3595112
industryworker3595112

24

Penso che sia importante fare una distinzione tra funzioni del generatore e generatori (risultato della funzione del generatore):

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

la chiamata a generator_function non produrrà un risultato normale, nemmeno eseguirà alcun codice nella funzione stessa, il risultato sarà un oggetto speciale chiamato generator :

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

quindi non è la funzione generatore, ma generatore:

>>> inspect.isgeneratorfunction(generator)
False

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

e la funzione generatore non è generatore:

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

solo per riferimento, l'effettivo richiamo del corpo della funzione avverrà consumando il generatore, ad esempio:

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

Vedi anche In python c'è un modo per verificare se una funzione è una "funzione generatore" prima di chiamarla?


11

La inspect.isgeneratorfunzione va bene se si desidera verificare la presenza di generatori puri (ovvero oggetti della classe "generatore"). Tuttavia, verrà restituito Falsese si seleziona, ad esempio, un izipiterabile. Un modo alternativo per verificare la presenza di un generatore generalizzato è utilizzare questa funzione:

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

1
Hmm. Questo ritorna vero per x=iter([1,2]). Mi sembra che stia davvero testando se un oggetto è o meno un iteratore , non un generatore. Ma forse "iteratore" è esattamente ciò che intendi per "generatore generalizzato".
Josh O'Brien,

3

È possibile utilizzare l'Iteratore o, più specificamente, il generatore dal modulo di digitazione .

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

risultato:

<class 'generator'>
True
True

1
+1 per una soluzione funzionante. Detto questo, i documenti per la typing.TypeVarclasse sembrano scoraggiare l'uso isinstancecongiuntamente al typingmodulo: "In fase di esecuzione, isinstance(x, T)aumenterà TypeError. In generale, isinstance()e issubclass()non dovrebbe essere usato con i tipi".
Jasha,

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

Funziona solo se è una funzione. Se "pippo" è un oggetto generatore, mostra "Falso". Vedi la mia domanda, voglio fare controlli per gli oggetti del generatore.
Pushpak Dagade,

2

So di poter verificare se l'oggetto ha un metodo successivo per essere un generatore, ma voglio in qualche modo utilizzare il quale posso determinare il tipo di qualsiasi oggetto, non solo i generatori.

Non farlo È semplicemente una pessima idea.

Invece, fai questo:

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

Nel caso improbabile che il corpo del ciclo for abbia anche TypeErrors, ci sono diverse scelte: (1) definire una funzione per limitare l'ambito degli errori o (2) usare un blocco di prova nidificato .

O (3) qualcosa del genere per distinguere tutte queste cose TypeErrorche fluttuano intorno.

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`

Oppure (4) correggere le altre parti dell'applicazione per fornire i generatori in modo appropriato. Questo è spesso più semplice di tutto questo.


1
La tua soluzione catturerà TypeErrors lanciati dal corpo del ciclo for. Ho proposto una modifica che impedirebbe questo comportamento indesiderato.
Dunes,

Questo è il modo più pitone di farlo, se non sbaglio.
JAB,

Anche se, se stai iterando su un elenco di elementi e molti di essi non sono iteratori rispetto agli iteratori, ciò potrebbe richiedere sicuramente più tempo?
Jakob Bowyer,

1
@Jakob Bowyer: le eccezioni sono più veloci delle ifdichiarazioni. E. Questo tipo di micro-ottimizzazione è una perdita di tempo. Correggi l'algoritmo che produce un miscuglio di iteratori e non iteratori per produrre solo iteratori e salvare te stesso tutto questo dolore.
S. Lott,

10
Ciò presuppone erroneamente qualsiasi iterabile come generatore.
Balki,

1

Se stai usando un server web tornado o simile potresti aver scoperto che i metodi server sono in realtà generatori e non metodi. Ciò rende difficile chiamare altri metodi perché il rendimento non funziona all'interno del metodo e quindi è necessario iniziare a gestire pool di oggetti generatori concatenati. Un metodo semplice per gestire gruppi di generatori concatenati è quello di creare una funzione di aiuto come

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

Ora scrivendo generatori concatenati come

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

Produce output

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

Che è probabilmente quello che vuoi se stai cercando di usare i generatori come alternativa al thread o simili.


1

(So ​​che è un vecchio post.) Non è necessario importare un modulo, puoi dichiarare un oggetto per il confronto all'inizio del programma:

gentyp= type(1 for i in "")                                                                                          
       ...
type(myobject) == gentyp
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.