Skip to content
26 changes: 17 additions & 9 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
# See Include/object.h
TPFLAGS_IS_ABSTRACT = 1 << 20

def _unwrap_partial(func):
while isinstance(func, functools.partial):
func = func.func
return func

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to move this helper function to Lib/functools.py.


# ----------------------------------------------------------- type-checking
def ismodule(object):
"""Return true if the object is a module.
Expand Down Expand Up @@ -168,30 +173,33 @@ def isfunction(object):
__kwdefaults__ dict of keyword only parameters with defaults"""
return isinstance(object, types.FunctionType)

def isgeneratorfunction(object):
def isgeneratorfunction(obj):
"""Return true if the object is a user-defined generator function.

Generator function objects provide the same attributes as functions.
See help(isfunction) for a list of attributes."""
return bool((isfunction(object) or ismethod(object)) and
object.__code__.co_flags & CO_GENERATOR)
obj = _unwrap_partial(obj)
return bool((isfunction(obj) or ismethod(obj)) and
obj.__code__.co_flags & CO_GENERATOR)

def iscoroutinefunction(object):
def iscoroutinefunction(obj):
"""Return true if the object is a coroutine function.

Coroutine functions are defined with "async def" syntax.
"""
return bool((isfunction(object) or ismethod(object)) and
object.__code__.co_flags & CO_COROUTINE)
obj = _unwrap_partial(obj)
return bool(((isfunction(obj) or ismethod(obj)) and
obj.__code__.co_flags & CO_COROUTINE))

def isasyncgenfunction(object):
def isasyncgenfunction(obj):
"""Return true if the object is an asynchronous generator function.

Asynchronous generator functions are defined with "async def"
syntax and have "yield" expressions in their body.
"""
return bool((isfunction(object) or ismethod(object)) and
object.__code__.co_flags & CO_ASYNC_GENERATOR)
obj = _unwrap_partial(obj)
return bool((isfunction(obj) or ismethod(obj)) and
obj.__code__.co_flags & CO_ASYNC_GENERATOR)

def isasyncgen(object):
"""Return true if the object is an asynchronous generator."""
Expand Down
27 changes: 26 additions & 1 deletion Lib/test/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,26 +166,51 @@ def test_excluding_predicates(self):
self.assertFalse(inspect.ismemberdescriptor(datetime.timedelta.days))

def test_iscoroutine(self):
async_gen_coro = async_generator_function_example(1)
gen_coro = gen_coroutine_function_example(1)
coro = coroutine_function_example(1)

self.assertFalse(
inspect.iscoroutinefunction(gen_coroutine_function_example))
self.assertFalse(
inspect.iscoroutinefunction(
functools.partial(functools.partial(
gen_coroutine_function_example))))
self.assertFalse(inspect.iscoroutine(gen_coro))

self.assertTrue(
inspect.isgeneratorfunction(gen_coroutine_function_example))
self.assertTrue(
inspect.isgeneratorfunction(
functools.partial(functools.partial(
gen_coroutine_function_example))))
self.assertTrue(inspect.isgenerator(gen_coro))

self.assertTrue(
inspect.iscoroutinefunction(coroutine_function_example))
self.assertTrue(
inspect.iscoroutinefunction(
functools.partial(functools.partial(
coroutine_function_example))))
self.assertTrue(inspect.iscoroutine(coro))

self.assertFalse(
inspect.isgeneratorfunction(coroutine_function_example))
self.assertFalse(
inspect.isgeneratorfunction(
functools.partial(functools.partial(
coroutine_function_example))))
self.assertFalse(inspect.isgenerator(coro))

coro.close(); gen_coro.close() # silence warnings
self.assertTrue(
inspect.isasyncgenfunction(async_generator_function_example))
self.assertTrue(
inspect.isasyncgenfunction(
functools.partial(functools.partial(
async_generator_function_example))))
self.assertTrue(inspect.isasyncgen(async_gen_coro))

coro.close(); gen_coro.close(); # silence warnings

def test_isawaitable(self):
def gen(): yield
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Make :func:`inspect.iscoroutinefunction`,
:func:`inspect.isgeneratorfunction` and :func:`inspect.isasyncgenfunction`
work with :func:`functools.partial`. Patch by Pablo Galindo.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also document the change with a ".. versionchanged:: 3.8" markup in https://docs.python.org/dev/library/inspect.html#inspect.isgeneratorfunction ?