Title: inspect.isgeneratorfunction not working with partial functions
Type: enhancement Stage: resolved
Components: Versions: Python 3.7
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: Thomas Antony, martin.panter, terry.reedy
Priority: normal Keywords:

Created on 2017-04-04 23:35 by Thomas Antony, last changed 2017-04-07 20:27 by terry.reedy. This issue is now closed.

File name Uploaded Description Edit Thomas Antony, 2017-04-04 23:35 Test code demonstrating the bug
Messages (6)
msg291147 - (view) Author: Thomas Antony (Thomas Antony) Date: 2017-04-04 23:35
When inspect.isgeneratorfunction is called on the output of functools.partial, it returns False even if the original function was a generator function. Test case is attached. 

Tested in fresh conda environment running Python 3.6.1
msg291150 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2017-04-05 02:01
Doesn't seem like a bug to me.

Even if there was special support for "partial" objects, that won't help with other ways of producing the same sort of thing.

test2 = functools.partial(test, a=10)

def test2():
    return test(a=10)

Both ways produce a callable that returns a generator-iterator instance, but neither callables are really generator functions.
msg291156 - (view) Author: Thomas Antony (Thomas Antony) Date: 2017-04-05 05:40
Is there any way to distinguish such callables from "normal" functions without actually calling them?
msg291169 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2017-04-05 08:49
Not in general. I think you would have to make special cases for partial functions, __wrapped__, and whatever else there is, and combinations of these. It would be very hard to determine the correct result for test2 in

test2 = lambda: test(a=10)  # test2() returns a generator

def test(a):
    '''Redefined as a non-generator!'''
# Now test2() returns None

Maybe there is an argument for supporting partial as a new feature, but I don’t think it is a bug. I think there is precedent with the inspect.getargspec etc.
msg291290 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2017-04-07 20:23
A generator function *gf* is a function that contain *yield* in its body.  When it is compiled, flag CO_GENERATOR is set in gf.__code__.co_flags.  When the function is called, the flag is queried and if set, a special path is taken that attaches the live but suspended code instance to a generator instance.

*isgeneratorfunction* tests that an object is a function and that the flag is set.

    return bool((isfunction(object) or ismethod(object)) and
                object.__code__.co_flags & CO_GENERATOR)

This is exactly what it should do.

Any function could potentially call a generator function and return its result.  Martin's *test2*, with or without the wrapper, is an example.

Note that *iscoroutinefunction* and *isaynchgenfunction* follow the same pattern, but with different flags.  A 4th flag is queried by *isawaitable*. The return object of any of these could also by returned by other functions.
msg291291 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2017-04-07 20:27
If you want to know if a function returns a generator, you have to call it and pass the result to *isgenerator*, or use type-annotated functions (see the typing module) and static type checkers.
Date User Action Args
2017-04-07 20:27:44terry.reedysetmessages: + msg291291
2017-04-07 20:23:13terry.reedysetstatus: open -> closed

type: enhancement
versions: + Python 3.7, - Python 3.5, Python 3.6
nosy: + terry.reedy

messages: + msg291290
resolution: rejected
stage: resolved
2017-04-05 08:49:12martin.pantersetmessages: + msg291169
2017-04-05 05:40:38Thomas Antonysetmessages: + msg291156
2017-04-05 02:01:08martin.pantersetnosy: + martin.panter
messages: + msg291150
2017-04-04 23:35:57Thomas Antonysetversions: + Python 3.6
2017-04-04 23:35:15Thomas Antonycreate