Skip to content
24 changes: 15 additions & 9 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,30 +168,36 @@ 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)
while isinstance(obj, functools.partial):
obj = obj.func
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)
while isinstance(obj, functools.partial):
obj = obj.func
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)
while isinstance(obj, functools.partial):

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.

Why don't we add an _unwrap_partial helper. Or.. should we maybe use inspect.unwrap() too?

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.

Actually, let's not use inspect.unwrap(). Decorating generators and coroutines isn't that common.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Do we still want the helper?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I have done the refactor in 3b841ac

obj = obj.func
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
23 changes: 22 additions & 1 deletion Lib/test/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import collections
import datetime
import functools
from functools import partial

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.

Please use module import (i.e. import functools).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in adb5db6. I did that originally to simplify the double functools.partial wrapping in the tests.

import importlib
import inspect
import io
Expand Down Expand Up @@ -166,26 +167,46 @@ 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(
partial(partial(gen_coroutine_function_example))))
self.assertFalse(inspect.iscoroutine(gen_coro))

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

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

self.assertFalse(
inspect.isgeneratorfunction(coroutine_function_example))
self.assertFalse(
inspect.isgeneratorfunction(
partial(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(
partial(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 ?