mirror of
https://github.com/python/cpython.git
synced 2024-11-23 18:04:37 +08:00
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.
This commit is contained in:
parent
e483f02423
commit
7cd2543416
@ -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:
|
|||||||
|
|
||||||
.. 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:
|
|||||||
|
|
||||||
.. 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):
|
|||||||
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):
|
|||||||
__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:
|
|||||||
|
|
||||||
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):
|
|||||||
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
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
Make :func:`inspect.iscoroutinefunction`,
|
||||||
|
:func:`inspect.isgeneratorfunction` and :func:`inspect.isasyncgenfunction`
|
||||||
|
work with :func:`functools.partial`. Patch by Pablo Galindo.
|
Loading…
Reference in New Issue
Block a user