8000 bpo-34890: Make iscoroutinefunction, isgeneratorfunction and isasyncg… · python/cpython@7cd2543 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7cd2543

Browse files
authored
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.
1 parent e483f02 commit 7cd2543

File tree

6 files changed

+61
-12
lines changed

6 files changed

+61
-12
lines changed

Doc/library/inspect.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,10 @@ attributes:
298298

299299
Return true if the object is a Python generator function.
300300

301+
.. versionchanged:: 3.8
302+
Functions wrapped in :func:`functools.partial` now return true if the
303+
wrapped function is a Python generator function.
304+
301305

302306
.. function:: isgenerator(object)
303307

@@ -311,6 +315,10 @@ attributes:
311315

312316
.. versionadded:: 3.5
313317

318+
.. versionchanged:: 3.8
319+
Functions wrapped in :func:`functools.partial` now return true if the
320+
wrapped function is a :term:`coroutine function`.
321+
314322

315323
.. function:: iscoroutine(object)
316324

@@ -352,6 +360,10 @@ attributes:
352360

353361
.. versionadded:: 3.6
354362

363+
.. versionchanged:: 3.8
364+
Functions wrapped in :func:`functools.partial` now return true if the
365+
wrapped function is a :term:`asynchronous generator` function.
366+
355367

356368
.. function:: isasyncgen(object)
357369

Lib/functools.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,12 @@ def __get__(self, obj, cls):
423423
def __isabstractmethod__(self):
424424
return getattr(self.func, "__isabstractmethod__", False)
425425

426+
# Helper functions
427+
428+
def _unwrap_partial(func):
429+
while isinstance(func, partial):
430+
func = func.func
431+
return func
426432

427433
################################################################################
428434
### LRU Cache function decorator

Lib/inspect.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -168,30 +168,33 @@ def isfunction(object):
168168
__kwdefaults__ dict of keyword only parameters with defaults"""
169169
return isinstance(object, types.FunctionType)
170170

171-
def isgeneratorfunction(object):
171+
def isgeneratorfunction(obj):
172172
"""Return true if the object is a user-defined generator function.
173173
174174
Generator function objects provide the same attributes as functions.
175175
See help(isfunction) for a list of attributes."""
176-
return bool((isfunction(object) or ismethod(object)) and
177-
object.__code__.co_flags & CO_GENERATOR)
176+
obj = functools._unwrap_partial(obj)
177+
return bool((isfunction(obj) or ismethod(obj)) and
178+
obj.__code__.co_flags & CO_GENERATOR)
178179

179-
def iscoroutinefunction(object):
180+
def iscoroutinefunction(obj):
180181
"""Return true if the object is a coroutine function.
181182
182183
Coroutine functions are defined with "async def" syntax.
183184
"""
184-
return bool((isfunction(object) or ismethod(object)) and
185-
object.__code__.co_flags & CO_COROUTINE)
185+
obj = functools._unwrap_partial(obj)
186+
return bool(((isfunction(obj) or ismethod(obj)) and
187+
obj.__code__.co_flags & CO_COROUTINE))
186188

187-
def isasyncgenfunction(object):
189+
def isasyncgenfunction(obj):
188190
"""Return true if the object is an asynchronous generator function.
189191
190192
Asynchronous generator functions are defined with "async def"
191193
syntax and have "yield" expressions in their body.
192194
"""
193-
return bool((isfunction(object) or ismethod(object)) and
194-
object.__code__.co_flags & CO_ASYNC_GENERATOR)
195+
obj = functools._unwrap_partial(obj)
196+
return bool((isfunction(obj) or ismethod(obj)) and
197+
obj.__code__.co_flags & CO_ASYNC_GENERATOR)
195198

196199
def isasyncgen(object):
197200
"""Return true if the object is an asynchronous generator."""

Lib/test/test_asyncio/test_tasks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,8 +440,8 @@ async def func(x, y):
440440

441441
coro_repr = repr(task._coro)
442442
expected = (
443-
r'<CoroWrapper \w+.test_task_repr_partial_corowrapper'
444-
r'\.<locals>\.func\(1\)\(\) running, '
443+
r'<coroutine object \w+\.test_task_repr_partial_corowrapper'
444+
r'\.<locals>\.func at'
445445
)
446446
self.assertRegex(coro_repr, expected)
447447

Lib/test/test_inspect.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,26 +166,51 @@ def test_excluding_predicates(self):
166166
self.assertFalse(inspect.ismemberdescriptor(datetime.timedelta.days))
167167

168168
def test_iscoroutine(self):
169+
async_gen_coro = async_generator_function_example(1)
169170
gen_coro = gen_coroutine_function_example(1)
170171
coro = coroutine_function_example(1)
171172

172173
self.assertFalse(
173174
inspect.iscoroutinefunction(gen_coroutine_function_example))
175+
self.assertFalse(
176+
inspect.iscoroutinefunction(
177+
functools.partial(functools.partial(
178+
gen_coroutine_function_example))))
174179
self.assertFalse(inspect.iscoroutine(gen_coro))
175180

176181
self.assertTrue(
177182
inspect.isgeneratorfunction(gen_coroutine_function_example))
183+
self.assertTrue(
184+
inspect.isgeneratorfunction(
185+
functools.partial(functools.partial(
186+
gen_coroutine_function_example))))
178187
self.assertTrue(inspect.isgenerator(gen_coro))
179188

180189
self.assertTrue(
181190
inspect.iscoroutinefunction(coroutine_function_example))
191+
self.assertTrue(
192+
inspect.iscoroutinefunction(
193+
functools.partial(functools.partial(
194+
coroutine_function_example))))
182195
self.assertTrue(inspect.iscoroutine(coro))
183196

184197
self.assertFalse(
185198
inspect.isgeneratorfunction(coroutine_function_example))
199+
self.assertFalse(
200+
inspect.isgeneratorfunction(
201+
functools.partial(functools.partial(
202+
coroutine_function_example))))
186203
self.assertFalse(inspect.isgenerator(coro))
187204

188-
coro.close(); gen_coro.close() # silence warnings
205+
self.assertTrue(
206+
inspect.isasyncgenfunction(async_generator_function_example))
207+
self.assertTrue(
208+
inspect.isasyncgenfunction(
209+
functools.partial(functools.partial(
210+
async_generator_function_example))))
211+
self.assertTrue(inspect.isasyncgen(async_gen_coro))
212+
213+
coro.close(); gen_coro.close(); # silence warnings
189214

190215
def test_isawaitable(self):
191216
def gen(): yield
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Make :func:`inspect.iscoroutinefunction`,
2+
:func:`inspect.isgeneratorfunction` and :func:`inspect.isasyncgenfunction`
3+
work with :func:`functools.partial`. Patch by Pablo Galindo.

0 commit comments

Comments
 (0)
0