Commit 7cd25434 authored by Pablo Galindo's avatar Pablo Galindo Committed by GitHub

bpo-34890: Make iscoroutinefunction, isgeneratorfunction and...

bpo-34890: Make iscoroutinefunction, isgeneratorfunction and isasyncgenfunction work with functools.partial (GH-9903)

inspect.isfunction() processes both inspect.isfunction(func) and
inspect.isfunction(partial(func, arg)) correctly but some other functions in the
inspect module (iscoroutinefunction, isgeneratorfunction and isasyncgenfunction)
lack this functionality. This commits adds a new check in the mentioned functions
in the inspect module so they can work correctly with arbitrarily nested partial
functions. 
parent e483f024
...@@ -298,6 +298,10 @@ attributes: ...@@ -298,6 +298,10 @@ attributes:
Return true if the object is a Python generator function. Return true if the object is a Python generator function.
.. versionchanged:: 3.8
Functions wrapped in :func:`functools.partial` now return true if the
wrapped function is a Python generator function.
.. function:: isgenerator(object) .. function:: isgenerator(object)
...@@ -311,6 +315,10 @@ attributes: ...@@ -311,6 +315,10 @@ attributes:
.. versionadded:: 3.5 .. versionadded:: 3.5
.. versionchanged:: 3.8
Functions wrapped in :func:`functools.partial` now return true if the
wrapped function is a :term:`coroutine function`.
.. function:: iscoroutine(object) .. function:: iscoroutine(object)
...@@ -352,6 +360,10 @@ attributes: ...@@ -352,6 +360,10 @@ attributes:
.. versionadded:: 3.6 .. versionadded:: 3.6
.. versionchanged:: 3.8
Functions wrapped in :func:`functools.partial` now return true if the
wrapped function is a :term:`asynchronous generator` function.
.. function:: isasyncgen(object) .. function:: isasyncgen(object)
......
...@@ -423,6 +423,12 @@ class partialmethod(object): ...@@ -423,6 +423,12 @@ class partialmethod(object):
def __isabstractmethod__(self): def __isabstractmethod__(self):
return getattr(self.func, "__isabstractmethod__", False) return getattr(self.func, "__isabstractmethod__", False)
# Helper functions
def _unwrap_partial(func):
while isinstance(func, partial):
func = func.func
return func
################################################################################ ################################################################################
### LRU Cache function decorator ### LRU Cache function decorator
......
...@@ -168,30 +168,33 @@ def isfunction(object): ...@@ -168,30 +168,33 @@ def isfunction(object):
__kwdefaults__ dict of keyword only parameters with defaults""" __kwdefaults__ dict of keyword only parameters with defaults"""
return isinstance(object, types.FunctionType) return isinstance(object, types.FunctionType)
def isgeneratorfunction(object): def isgeneratorfunction(obj):
"""Return true if the object is a user-defined generator function. """Return true if the object is a user-defined generator function.
Generator function objects provide the same attributes as functions. Generator function objects provide the same attributes as functions.
See help(isfunction) for a list of attributes.""" See help(isfunction) for a list of attributes."""
return bool((isfunction(object) or ismethod(object)) and obj = functools._unwrap_partial(obj)
object.__code__.co_flags & CO_GENERATOR) 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. """Return true if the object is a coroutine function.
Coroutine functions are defined with "async def" syntax. Coroutine functions are defined with "async def" syntax.
""" """
return bool((isfunction(object) or ismethod(object)) and obj = functools._unwrap_partial(obj)
object.__code__.co_flags & CO_COROUTINE) 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. """Return true if the object is an asynchronous generator function.
Asynchronous generator functions are defined with "async def" Asynchronous generator functions are defined with "async def"
syntax and have "yield" expressions in their body. syntax and have "yield" expressions in their body.
""" """
return bool((isfunction(object) or ismethod(object)) and obj = functools._unwrap_partial(obj)
object.__code__.co_flags & CO_ASYNC_GENERATOR) return bool((isfunction(obj) or ismethod(obj)) and
obj.__code__.co_flags & CO_ASYNC_GENERATOR)
def isasyncgen(object): def isasyncgen(object):
"""Return true if the object is an asynchronous generator.""" """Return true if the object is an asynchronous generator."""
......
...@@ -440,8 +440,8 @@ class BaseTaskTests: ...@@ -440,8 +440,8 @@ class BaseTaskTests:
coro_repr = repr(task._coro) coro_repr = repr(task._coro)
expected = ( expected = (
r'<CoroWrapper \w+.test_task_repr_partial_corowrapper' r'<coroutine object \w+\.test_task_repr_partial_corowrapper'
r'\.<locals>\.func\(1\)\(\) running, ' r'\.<locals>\.func at'
) )
self.assertRegex(coro_repr, expected) self.assertRegex(coro_repr, expected)
......
...@@ -166,26 +166,51 @@ class TestPredicates(IsTestBase): ...@@ -166,26 +166,51 @@ class TestPredicates(IsTestBase):
self.assertFalse(inspect.ismemberdescriptor(datetime.timedelta.days)) self.assertFalse(inspect.ismemberdescriptor(datetime.timedelta.days))
def test_iscoroutine(self): def test_iscoroutine(self):
async_gen_coro = async_generator_function_example(1)
gen_coro = gen_coroutine_function_example(1) gen_coro = gen_coroutine_function_example(1)
coro = coroutine_function_example(1) coro = coroutine_function_example(1)
self.assertFalse( self.assertFalse(
inspect.iscoroutinefunction(gen_coroutine_function_example)) 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.assertFalse(inspect.iscoroutine(gen_coro))
self.assertTrue( self.assertTrue(
inspect.isgeneratorfunction(gen_coroutine_function_example)) 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.isgenerator(gen_coro))
self.assertTrue( self.assertTrue(
inspect.iscoroutinefunction(coroutine_function_example)) inspect.iscoroutinefunction(coroutine_function_example))
self.assertTrue(
inspect.iscoroutinefunction(
functools.partial(functools.partial(
coroutine_function_example))))
self.assertTrue(inspect.iscoroutine(coro)) self.assertTrue(inspect.iscoroutine(coro))
self.assertFalse( self.assertFalse(
inspect.isgeneratorfunction(coroutine_function_example)) inspect.isgeneratorfunction(coroutine_function_example))
self.assertFalse(
inspect.isgeneratorfunction(
functools.partial(functools.partial(
coroutine_function_example))))
self.assertFalse(inspect.isgenerator(coro)) 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 test_isawaitable(self):
def gen(): yield def gen(): yield
......
Make :func:`inspect.iscoroutinefunction`,
:func:`inspect.isgeneratorfunction` and :func:`inspect.isasyncgenfunction`
work with :func:`functools.partial`. Patch by Pablo Galindo.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment