Skip to content

Commit c6d817f

Browse files
committed
bpo-38364: unwrap partialmethods just like we unwrap partials
The inspect.isgeneratorfunction, inspect.iscoroutinefunction and inspect.isasyncgenfunction already unwrap functools.partial objects, this patch adds support for partialmethod objects as well.
1 parent 65dcc8a commit c6d817f

File tree

4 files changed

+49
-4
lines changed

4 files changed

+49
-4
lines changed

Doc/library/inspect.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,8 @@ attributes:
302302
Return true if the object is a Python generator function.
303303

304304
.. versionchanged:: 3.8
305-
Functions wrapped in :func:`functools.partial` now return true if the
305+
Functions wrapped in :func:`functools.partial` or
306+
:func:`functools.partialmethod` now return true if the
306307
wrapped function is a Python generator function.
307308

308309

@@ -319,7 +320,8 @@ attributes:
319320
.. versionadded:: 3.5
320321

321322
.. versionchanged:: 3.8
322-
Functions wrapped in :func:`functools.partial` now return true if the
323+
Functions wrapped in :func:`functools.partial` or
324+
:func:`functools.partialmethod` now return true if the
323325
wrapped function is a :term:`coroutine function`.
324326

325327

@@ -364,7 +366,8 @@ attributes:
364366
.. versionadded:: 3.6
365367

366368
.. versionchanged:: 3.8
367-
Functions wrapped in :func:`functools.partial` now return true if the
369+
Functions wrapped in :func:`functools.partial` or
370+
:func:`functools.partialmethod` now return true if the
368371
wrapped function is a :term:`asynchronous generator` function.
369372

370373

Lib/functools.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,17 @@ def _unwrap_partial(func):
413413
func = func.func
414414
return func
415415

416+
def _unwrap_partialmethod(func):
417+
prev = None
418+
while func is not prev:
419+
prev = func
420+
while hasattr(func, "_partialmethod"):
421+
func = getattr(func, '_partialmethod')
422+
while isinstance(func, partialmethod):
423+
func = getattr(func, 'func')
424+
func = _unwrap_partial(func)
425+
return func
426+
416427
################################################################################
417428
### LRU Cache function decorator
418429
################################################################################

Lib/inspect.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,10 @@ def isfunction(object):
170170

171171
def _has_code_flag(f, flag):
172172
"""Return true if ``f`` is a function (or a method or functools.partial
173-
wrapper wrapping a function) whose code object has the given ``flag``
173+
wrapper wrapping a function or a functools.partialmethod wrapping a
174+
function) whose code object has the given ``flag``
174175
set in its flags."""
176+
f = functools._unwrap_partialmethod(f)
175177
while ismethod(f):
176178
f = f.__func__
177179
f = functools._unwrap_partial(f)

Lib/test/test_inspect.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,33 @@ def test_iscoroutine(self):
167167
gen_coro = gen_coroutine_function_example(1)
168168
coro = coroutine_function_example(1)
169169

170+
class PMClass:
171+
async_generator_partialmethod_example = functools.partialmethod(
172+
async_generator_function_example)
173+
coroutine_partialmethod_example = functools.partialmethod(
174+
coroutine_function_example)
175+
gen_coroutine_partialmethod_example = functools.partialmethod(
176+
gen_coroutine_function_example)
177+
178+
# partialmethods on the class, bound to an instance
179+
pm_instance = PMClass()
180+
async_gen_coro_pmi = pm_instance.async_generator_partialmethod_example
181+
gen_coro_pmi = pm_instance.gen_coroutine_partialmethod_example
182+
coro_pmi = pm_instance.coroutine_partialmethod_example
183+
184+
# partialmethods on the class, unbound but accessed via the class
185+
async_gen_coro_pmc = PMClass.async_generator_partialmethod_example
186+
gen_coro_pmc = PMClass.gen_coroutine_partialmethod_example
187+
coro_pmc = PMClass.coroutine_partialmethod_example
188+
170189
self.assertFalse(
171190
inspect.iscoroutinefunction(gen_coroutine_function_example))
172191
self.assertFalse(
173192
inspect.iscoroutinefunction(
174193
functools.partial(functools.partial(
175194
gen_coroutine_function_example))))
195+
self.assertFalse(inspect.iscoroutinefunction(gen_coro_pmi))
196+
self.assertFalse(inspect.iscoroutinefunction(gen_coro_pmc))
176197
self.assertFalse(inspect.iscoroutine(gen_coro))
177198

178199
self.assertTrue(
@@ -181,6 +202,8 @@ def test_iscoroutine(self):
181202
inspect.isgeneratorfunction(
182203
functools.partial(functools.partial(
183204
gen_coroutine_function_example))))
205+
self.assertTrue(inspect.isgeneratorfunction(gen_coro_pmi))
206+
self.assertTrue(inspect.isgeneratorfunction(gen_coro_pmc))
184207
self.assertTrue(inspect.isgenerator(gen_coro))
185208

186209
self.assertTrue(
@@ -189,6 +212,8 @@ def test_iscoroutine(self):
189212
inspect.iscoroutinefunction(
190213
functools.partial(functools.partial(
191214
coroutine_function_example))))
215+
self.assertTrue(inspect.iscoroutinefunction(coro_pmi))
216+
self.assertTrue(inspect.iscoroutinefunction(coro_pmc))
192217
self.assertTrue(inspect.iscoroutine(coro))
193218

194219
self.assertFalse(
@@ -197,6 +222,8 @@ def test_iscoroutine(self):
197222
inspect.isgeneratorfunction(
198223
functools.partial(functools.partial(
199224
coroutine_function_example))))
225+
self.assertFalse(inspect.isgeneratorfunction(coro_pmi))
226+
self.assertFalse(inspect.isgeneratorfunction(coro_pmc))
200227
self.assertFalse(inspect.isgenerator(coro))
201228

202229
self.assertTrue(
@@ -205,6 +232,8 @@ def test_iscoroutine(self):
205232
inspect.isasyncgenfunction(
206233
functools.partial(functools.partial(
207234
async_generator_function_example))))
235+
self.assertTrue(inspect.isasyncgenfunction(async_gen_coro_pmi))
236+
self.assertTrue(inspect.isasyncgenfunction(async_gen_coro_pmc))
208237
self.assertTrue(inspect.isasyncgen(async_gen_coro))
209238

210239
coro.close(); gen_coro.close(); # silence warnings

0 commit comments

Comments
 (0)