Como verificar se um objeto é um objeto gerador em python?

157

Em python, como verifico se um objeto é um objeto gerador?

Tentando isso -

>>> type(myobject, generator)

dá o erro -

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

(Eu sei que posso verificar se o objeto tem um nextmétodo para ser um gerador, mas quero usar um meio para determinar o tipo de qualquer objeto, não apenas geradores.)

Pushpak Dagade
fonte
4
Que problema real você está tentando resolver? Poste mais contexto, pode haver uma maneira mais inteligente. Por que você precisa saber se é um gerador?
Daenyth
7
from types import GeneratorType;type(myobject, GeneratorType)lhe dará o resultado adequado para objetos da classe 'gerador'. Mas, como Daenyth implica, esse não é necessariamente o caminho certo a seguir.
JAB
7
Se você está procurando __next__, na verdade está aceitando qualquer iterador, não apenas geradores - o que provavelmente é o que você deseja.
2
Freqüentemente, o verdadeiro objetivo de saber se algo é um gerador é poder evitá-los, porque você deseja repetir a mesma coleção várias vezes.
26516 Ian
2
Para as pessoas que se perguntam sobre o caso de uso, isso pode ser útil quando você precisar saber se o iterador será consumido (por exemplo, se sua função aceitar qualquer iterador, mas precisar iterar mais de uma vez, convém materializá-lo antes da iteração)
wbadart 26/06

Respostas:

227

Você pode usar o GeneratorType dos tipos:

>>> import types
>>> types.GeneratorType
<class 'generator'>
>>> gen = (i for i in range(10))
>>> isinstance(gen, types.GeneratorType)
True
utdemir
fonte
5
Infelizmente, isso não funciona para classes de gerador (por exemplo, mapear ou filtrar objetos).
Ricardo Cruz
Talvez isinstance(gen, (types.GeneratorType, map, filter))seja útil também detectar mape filter. Isso ainda não inclui outros iteráveis ​​e iteradores.
jlh
38

Você quer dizer funções de gerador? use inspect.isgeneratorfunction.

EDIT:

se você deseja um objeto gerador, pode usar o inspect.isgenerator, como apontado pelo JAB em seu comentário.

mouad
fonte
1
função gerador não é objeto gerador; ver resposta da @ utdemir
Piotr Findeisen
5
@Piotr: Nesse caso, você usa inspect.isgenerator.
JAB
@JAB, @Piotr: refletida para o endereço de todas as possibilidades de que o OP pode significar, graças JAB :)
mouad
1
Nota: se você só precisa este teste, você pode evitar uma pequena sobrecarga usando @utdemir solução , porque inspect.isgeneratoré apenas uma abreviação para: isinstance(object, types.GeneratorType).
bufh
Consulte a resposta do @RobertLujo para obter uma distinção entre objeto gerador e função gerador. stackoverflow.com/a/32380774/3595112
industryworker3595112
24

Eu acho que é importante fazer distinção entre funções de gerador e geradores (resultado da função de gerador):

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

chamar a função generator_ não produzirá um resultado normal, nem executará nenhum código na própria função, o resultado será um objeto especial chamado generator :

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

portanto, não é função do gerador, mas gerador:

>>> inspect.isgeneratorfunction(generator)
False

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

e a função de gerador não é gerador:

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

apenas para uma referência, a chamada real do corpo da função ocorrerá consumindo o gerador, por exemplo:

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

Veja também Em python, existe uma maneira de verificar se uma função é uma "função geradora" antes de chamá-la?

Robert Lujo
fonte
11

A inspect.isgeneratorfunção está correta se você deseja procurar por geradores puros (ou seja, objetos da classe "gerador"). No entanto, ele retornará Falsese você marcar, por exemplo, um izipiterável. Uma maneira alternativa de verificar um gerador generalizado é usar esta função:

def isgenerator(iterable):
    return hasattr(iterable,'__iter__') and not hasattr(iterable,'__len__')
Luca Sbardella
fonte
1
Hmm. Isso retorna verdadeiro para x=iter([1,2]). Parece-me que está realmente testando se um objeto é ou não um iterador , não um gerador. Mas talvez "iterador" seja exatamente o que você quer dizer com "gerador generalizado".
Josh O'Brien
3

Você pode usar o iterador ou, mais especificamente, o gerador do módulo de digitação .

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
user9074332
fonte
1
+1 para uma solução funcional. Dito isto, os documentos para a typing.TypeVarclasse parecem desencorajar o uso isinstanceem conjunto com o typingmódulo: "Em tempo de execução, isinstance(x, T)aumentará TypeError. Em geral, isinstance()e issubclass()não deve ser usado com tipos".
Jasha
2
>>> import inspect
>>> 
>>> def foo():
...   yield 'foo'
... 
>>> print inspect.isgeneratorfunction(foo)
True
Corey Goldberg
fonte
Isso funciona apenas se for uma função. Se 'foo' for um objeto gerador, ele mostra 'False'. Veja minha pergunta, quero fazer verificações para objetos geradores.
Pushpak Dagade
2

Eu sei que posso verificar se o objeto tem um próximo método para que ele seja um gerador, mas quero uma maneira de determinar o tipo de qualquer objeto, não apenas geradores.

Não faça isso. É simplesmente uma ideia muito, muito ruim.

Em vez disso, faça o seguinte:

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

No caso improvável de o corpo do loop for também ter TypeErrors, existem várias opções: (1) defina uma função para limitar o escopo dos erros ou (2) use um bloco try aninhado .

Ou (3) algo assim para distinguir todos esses TypeErrors que estão flutuando.

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) corrija as outras partes do seu aplicativo para fornecer geradores adequadamente. Isso geralmente é mais simples do que tudo isso.

S.Lott
fonte
1
Sua solução capturará TypeErrors lançados pelo corpo do loop for. Propus uma edição que impediria esse comportamento indesejável.
Dunes
Esta é a maneira mais pitônica de fazer isso, se não me engano.
JAB
Embora, se você estiver iterando sobre uma lista de itens e mais deles não forem iteradores do que são iteradores, isso pode levar mais tempo com certeza?
Jakob Bowyer
1
@ Jakob Bowyer: Exceções são mais rápidas que ifdeclarações. E. Esse tipo de micro-otimização é uma perda de tempo. Corrija o algoritmo que produz uma mistura de iteradores e não iteradores para produzir apenas iteradores e economizar toda essa dor.
S.Lott
10
Isso assumiria por engano qualquer iterável como gerador.
balki
1

Se você estiver usando o servidor da web tornado ou semelhante, pode ter descoberto que os métodos do servidor são realmente geradores e não métodos. Isso dificulta a chamada de outros métodos porque o rendimento não está funcionando dentro do método e, portanto, você precisa iniciar o gerenciamento de conjuntos de objetos do gerador em cadeia. Um método simples para gerenciar pools de geradores encadeados é criar uma função de ajuda como

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

Agora, escrevendo geradores encadeados, como

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

Produz saída

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

Provavelmente, é o que você deseja se estiver procurando usar geradores como uma alternativa de thread ou semelhante.

user6830669
fonte
1

(Eu sei que é um post antigo.) Não há necessidade de importar um módulo, você pode declarar um objeto para comparação no início do programa:

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