Python Forum

Full Version: detecting a generstor passed to a funtion
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
what is the appropriate pythonic way for a function to detect that a generator is passed to it? i can do this:
if type(arg).__name__=='generator':
    print('got a generator')
else:
    print('did not get a generator')
i cannot do:
if isinstance(arg,generator):
    print('got a generator')
else:
    print('did not get a generator')
because generator is not defined.
from collections.abc import Generator
from typing import Generator as TypingGenerator
from inspect import isgeneratorfunction, isgenerator


def foo():
    """Function"""

def bar():
    """Generatorfunction"""
    yield


print("foo", isgeneratorfunction(foo))
print("bar", isgeneratorfunction(bar))

print("foo()", isgenerator(foo()))
print("bar()", isgenerator(bar()))

# Generator could not detect a Generatorfunction
# but a Generator

print("bar()", isinstance(bar(), Generator))
print("bar()", isinstance(bar(), TypingGenerator))
I'm not sure about typing.Generator. It detects a generator, but typing is used for type hints, which is used for IDEs and Code Linters. One question, many solutions...
(Sep-19-2021, 06:06 AM)DeaD_EyE Wrote: [ -> ]# Generator could not detect a Generatorfunction
Not sure what you mean with this comment. The output is

Output:
foo False bar True foo() False bar() True bar() True bar() True
note, blank lines between output pairs are mine.
Maybe looking for something like this?

>>> g = (x for x in range(20))
>>> g
<generator object <genexpr> at 0x10d4a6820>
>>> import types
>>> isinstance(g, types.GeneratorType)
True
(Sep-19-2021, 07:55 AM)buran Wrote: [ -> ]Not sure what you mean with this comment. The output is

from inspect import isgeneratorfunction


def normal_function():
    return None

def generator_function():
    yield


print("Type of normal_function", type(normal_function))
print("Type of generator_function", type(generator_function))
print("How to find out if generator_function is a generator without calling it?")
print("Return-Type of generator_function()", type(generator_function()))
print("Now the detection of a generator_function without calling this function")
print(isgeneratorfunction(generator_function))
Output:
Type of normal_function <class 'function'> Type of generator_function <class 'function'> How to find out if generator_function is a generator without calling it? Return-Type of generator_function() <class 'generator'> Now the detection of a generator_function without calling this function True
A simplified implementation from inspect just for functions, not methods.
(it could also detect generator methods, but the code in inspect is a bit different and does crazy wrapping)
CO_GENERATOR = 32


def isgeneratorfunction(func):
    if hasattr(func, "__code__"):
        return bool(func.__code__.co_flags & CO_GENERATOR)    

    return False
With typing.Generator and with collections.abc.Generator you can't detect if a function will return a generator without calling the function.
You can call the generator and without iterating it or use of the send method, the generator does not execute the code inside.
The thing is, that you call a function to check if it's a Generator and if it's the case, to throw it away or vice versa.
But there is another important point to bring up.

If you have a function which could be a generator, but you don't know it and another fact is, if you call the wrong function, it can take much time to compute something. But what if you have a worker with a bunch of functions/generators/coroutines and you need to branch to put the tasks into the right worker. The synchronous functions to the threaded/multiprocess-worker, the generators to an async worker aswell the coroutines. Calling a generatorfunction, return the generator. Calling a coroutine, returns the coroutine object. Calling a ordinary function, returns the result. But if you want to put this function with arguments to the threaded worker, you can't call the function inside the process which put the tasks to the right workers. So you need to be able to detect this, before you call a function.

The function has in the code-object flags, which define which type the function is.
The Flags:
Output:
CO_OPTIMIZED = 1 CO_NEWLOCALS = 2 CO_VARARGS = 4 CO_VARKEYWORDS = 8 CO_NESTED = 16 CO_GENERATOR = 32 CO_NOFREE = 64 CO_COROUTINE = 128 CO_ITERABLE_COROUTINE = 256 CO_ASYNC_GENERATOR = 512
You should be careful what you ask. The rabbit hole is quite deep.
(Sep-19-2021, 08:00 AM)bowlofred Wrote: [ -> ]Maybe looking for something like this?

>>> g = (x for x in range(20))
>>> g
<generator object <genexpr> at 0x10d4a6820>
>>> import types
>>> isinstance(g, types.GeneratorType)
True

so, could i do:
from types import GeneratorType as generator
followed by that 2nd failing example of mine?
(Sep-19-2021, 11:08 AM)DeaD_EyE Wrote: [ -> ]You should be careful what you ask. The rabbit hole is quite deep.

i was asking about the kind of generator that can be expressed between the () in a call to a function, but i probably should also be detecting for a callable function that has a yield statement, as determined by the compiler?
(Sep-20-2021, 05:42 PM)Skaperen Wrote: [ -> ]so, could i do:
from types import GeneratorType as generator
followed by that 2nd failing example of mine?

Looks good to me..

from types import GeneratorType as generator

arg = (x for x in range(5))
if isinstance(arg,generator):
    print('got a generator')
else:
    print('did not get a generator')
Output:
got a generator
i am happy, now. thanks!