]> granicus.if.org Git - python/commitdiff
bpo-34890: Make iscoroutinefunction, isgeneratorfunction and isasyncgenfunction work...
authorPablo Galindo <Pablogsal@gmail.com>
Fri, 26 Oct 2018 11:19:14 +0000 (12:19 +0100)
committerGitHub <noreply@github.com>
Fri, 26 Oct 2018 11:19:14 +0000 (12:19 +0100)
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.

Doc/library/inspect.rst
Lib/functools.py
Lib/inspect.py
Lib/test/test_asyncio/test_tasks.py
Lib/test/test_inspect.py
Misc/NEWS.d/next/Library/2018-10-15-23-10-41.bpo-34890.77E770.rst [new file with mode: 0644]

index 50cd00367a1b70b35f3eeed8d0e34d99130a3fa4..dfd78a97145c6f2e23d7c55a2991ec2e81273771 100644 (file)
@@ -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)
 
index 39a4af81d0510a0b7b553ea761afd2ffab93c7ab..ab7d71e126bd3061c8d3d68e94f8ba7b74d14610 100644 (file)
@@ -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
index 3edf97d389eab61ede83b2102ea778c49d2fd352..b8a142232b88a0fb35d966dc9e58a2a501fabe98 100644 (file)
@@ -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."""
index 0fe767630f1a9984ae6d49dd36e5e0936f266437..c65d1f2440d2e6106b88154f21f1661c66146645 100644 (file)
@@ -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)
 
index 134b0cd0b735c45244944a8ec72d7a818e7d1f04..b9072e0137eb087f7b1718e931e2bbd14373ce1e 100644 (file)
@@ -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 (file)
index 0000000..58745b2
--- /dev/null
@@ -0,0 +1,3 @@
+Make :func:`inspect.iscoroutinefunction`,
+:func:`inspect.isgeneratorfunction` and :func:`inspect.isasyncgenfunction`
+work with :func:`functools.partial`. Patch by Pablo Galindo.