diff options
author | Pablo Galindo <Pablogsal@gmail.com> | 2018-10-26 11:19:14 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-10-26 11:19:14 (GMT) |
commit | 7cd25434164882c2093ea41ccfc7b95a05cd5cbd (patch) | |
tree | 76c33543b495e78b0e520dd4914da68dcf616dae | |
parent | e483f02423917dc4dfd25f46e5b9e6fce304777d (diff) | |
download | cpython-7cd25434164882c2093ea41ccfc7b95a05cd5cbd.zip cpython-7cd25434164882c2093ea41ccfc7b95a05cd5cbd.tar.gz cpython-7cd25434164882c2093ea41ccfc7b95a05cd5cbd.tar.bz2 |
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.
-rw-r--r-- | Doc/library/inspect.rst | 12 | ||||
-rw-r--r-- | Lib/functools.py | 6 | ||||
-rw-r--r-- | Lib/inspect.py | 21 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_tasks.py | 4 | ||||
-rw-r--r-- | Lib/test/test_inspect.py | 27 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2018-10-15-23-10-41.bpo-34890.77E770.rst | 3 |
6 files changed, 61 insertions, 12 deletions
diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 50cd003..dfd78a9 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -298,6 +298,10 @@ attributes: 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) @@ -311,6 +315,10 @@ attributes: .. 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) @@ -352,6 +360,10 @@ attributes: .. 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) diff --git a/Lib/functools.py b/Lib/functools.py index 39a4af8..ab7d71e 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -423,6 +423,12 @@ class partialmethod(object): def __isabstractmethod__(self): 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 diff --git a/Lib/inspect.py b/Lib/inspect.py index 3edf97d..b8a1422 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -168,30 +168,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 = functools._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 = functools._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 = functools._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.""" diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 0fe7676..c65d1f2 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -440,8 +440,8 @@ class BaseTaskTests: coro_repr = repr(task._coro) expected = ( - r'<CoroWrapper \w+.test_task_repr_partial_corowrapper' - r'\.<locals>\.func\(1\)\(\) running, ' + r'<coroutine object \w+\.test_task_repr_partial_corowrapper' + r'\.<locals>\.func at' ) self.assertRegex(coro_repr, expected) diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 134b0cd..b9072e0 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -166,26 +166,51 @@ class TestPredicates(IsTestBase): 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 diff --git a/Misc/NEWS.d/next/Library/2018-10-15-23-10-41.bpo-34890.77E770.rst b/Misc/NEWS.d/next/Library/2018-10-15-23-10-41.bpo-34890.77E770.rst new file mode 100644 index 0000000..58745b2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-10-15-23-10-41.bpo-34890.77E770.rst @@ -0,0 +1,3 @@ +Make :func:`inspect.iscoroutinefunction`, +:func:`inspect.isgeneratorfunction` and :func:`inspect.isasyncgenfunction` +work with :func:`functools.partial`. Patch by Pablo Galindo. |