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

Pablo Galindo webhook-mailer at python.org
Fri Oct 26 07:19:19 EDT 2018


https://github.com/python/cpython/commit/7cd25434164882c2093ea41ccfc7b95a05cd5cbd
commit: 7cd25434164882c2093ea41ccfc7b95a05cd5cbd
branch: master
author: Pablo Galindo <Pablogsal at gmail.com>
committer: GitHub <noreply at github.com>
date: 2018年10月26日T12:19:14+01:00
summary:
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.
files:
A Misc/NEWS.d/next/Library/2018-10-15-23-10-41.bpo-34890.77E770.rst
M Doc/library/inspect.rst
M Lib/functools.py
M Lib/inspect.py
M Lib/test/test_asyncio/test_tasks.py
M Lib/test/test_inspect.py
diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index 50cd00367a1b..dfd78a97145c 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 39a4af81d051..ab7d71e126bd 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -423,6 +423,12 @@ def __get__(self, obj, cls):
 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 3edf97d389ea..b8a142232b88 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 0fe767630f1a..c65d1f2440d2 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -440,8 +440,8 @@ def test_task_repr_partial_corowrapper(self):
 
 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 134b0cd0b735..b9072e0137eb 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -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
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 000000000000..58745b289591
--- /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.


More information about the Python-checkins mailing list

AltStyle によって変換されたページ (->オリジナル) /