mirror of https://github.com/python/cpython
bpo-38364: unwrap partialmethods just like we unwrap partials (#16600)
* 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. Also: Rename _partialmethod to __partialmethod__. Since we're checking this attribute on arbitrary function-like objects, we should use the namespace reserved for core Python. --------- Co-authored-by: Petr Viktorin <encukou@gmail.com>
This commit is contained in:
parent
9e3729bbd7
commit
edb59d5718
|
@ -340,6 +340,9 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
|
|||
Functions wrapped in :func:`functools.partial` now return ``True`` if the
|
||||
wrapped function is a Python generator function.
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
Functions wrapped in :func:`functools.partialmethod` now return ``True``
|
||||
if the wrapped function is a Python generator function.
|
||||
|
||||
.. function:: isgenerator(object)
|
||||
|
||||
|
@ -363,6 +366,10 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
|
|||
Sync functions marked with :func:`markcoroutinefunction` now return
|
||||
``True``.
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
Functions wrapped in :func:`functools.partialmethod` now return ``True``
|
||||
if the wrapped function is a :term:`coroutine function`.
|
||||
|
||||
|
||||
.. function:: markcoroutinefunction(func)
|
||||
|
||||
|
@ -429,6 +436,9 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
|
|||
Functions wrapped in :func:`functools.partial` now return ``True`` if the
|
||||
wrapped function is a :term:`asynchronous generator` function.
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
Functions wrapped in :func:`functools.partialmethod` now return ``True``
|
||||
if the wrapped function is a :term:`coroutine function`.
|
||||
|
||||
.. function:: isasyncgen(object)
|
||||
|
||||
|
|
|
@ -388,7 +388,7 @@ class partialmethod(object):
|
|||
keywords = {**self.keywords, **keywords}
|
||||
return self.func(cls_or_self, *self.args, *args, **keywords)
|
||||
_method.__isabstractmethod__ = self.__isabstractmethod__
|
||||
_method._partialmethod = self
|
||||
_method.__partialmethod__ = self
|
||||
return _method
|
||||
|
||||
def __get__(self, obj, cls=None):
|
||||
|
@ -424,6 +424,17 @@ def _unwrap_partial(func):
|
|||
func = func.func
|
||||
return func
|
||||
|
||||
def _unwrap_partialmethod(func):
|
||||
prev = None
|
||||
while func is not prev:
|
||||
prev = func
|
||||
while isinstance(getattr(func, "__partialmethod__", None), partialmethod):
|
||||
func = func.__partialmethod__
|
||||
while isinstance(func, partialmethod):
|
||||
func = getattr(func, 'func')
|
||||
func = _unwrap_partial(func)
|
||||
return func
|
||||
|
||||
################################################################################
|
||||
### LRU Cache function decorator
|
||||
################################################################################
|
||||
|
|
|
@ -383,8 +383,10 @@ def isfunction(object):
|
|||
|
||||
def _has_code_flag(f, flag):
|
||||
"""Return true if ``f`` is a function (or a method or functools.partial
|
||||
wrapper wrapping a function) whose code object has the given ``flag``
|
||||
wrapper wrapping a function or a functools.partialmethod wrapping a
|
||||
function) whose code object has the given ``flag``
|
||||
set in its flags."""
|
||||
f = functools._unwrap_partialmethod(f)
|
||||
while ismethod(f):
|
||||
f = f.__func__
|
||||
f = functools._unwrap_partial(f)
|
||||
|
@ -2561,7 +2563,7 @@ def _signature_from_callable(obj, *,
|
|||
return sig
|
||||
|
||||
try:
|
||||
partialmethod = obj._partialmethod
|
||||
partialmethod = obj.__partialmethod__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
|
|
|
@ -206,12 +206,33 @@ class TestPredicates(IsTestBase):
|
|||
gen_coro = gen_coroutine_function_example(1)
|
||||
coro = coroutine_function_example(1)
|
||||
|
||||
class PMClass:
|
||||
async_generator_partialmethod_example = functools.partialmethod(
|
||||
async_generator_function_example)
|
||||
coroutine_partialmethod_example = functools.partialmethod(
|
||||
coroutine_function_example)
|
||||
gen_coroutine_partialmethod_example = functools.partialmethod(
|
||||
gen_coroutine_function_example)
|
||||
|
||||
# partialmethods on the class, bound to an instance
|
||||
pm_instance = PMClass()
|
||||
async_gen_coro_pmi = pm_instance.async_generator_partialmethod_example
|
||||
gen_coro_pmi = pm_instance.gen_coroutine_partialmethod_example
|
||||
coro_pmi = pm_instance.coroutine_partialmethod_example
|
||||
|
||||
# partialmethods on the class, unbound but accessed via the class
|
||||
async_gen_coro_pmc = PMClass.async_generator_partialmethod_example
|
||||
gen_coro_pmc = PMClass.gen_coroutine_partialmethod_example
|
||||
coro_pmc = PMClass.coroutine_partialmethod_example
|
||||
|
||||
self.assertFalse(
|
||||
inspect.iscoroutinefunction(gen_coroutine_function_example))
|
||||
self.assertFalse(
|
||||
inspect.iscoroutinefunction(
|
||||
functools.partial(functools.partial(
|
||||
gen_coroutine_function_example))))
|
||||
self.assertFalse(inspect.iscoroutinefunction(gen_coro_pmi))
|
||||
self.assertFalse(inspect.iscoroutinefunction(gen_coro_pmc))
|
||||
self.assertFalse(inspect.iscoroutine(gen_coro))
|
||||
|
||||
self.assertTrue(
|
||||
|
@ -220,6 +241,8 @@ class TestPredicates(IsTestBase):
|
|||
inspect.isgeneratorfunction(
|
||||
functools.partial(functools.partial(
|
||||
gen_coroutine_function_example))))
|
||||
self.assertTrue(inspect.isgeneratorfunction(gen_coro_pmi))
|
||||
self.assertTrue(inspect.isgeneratorfunction(gen_coro_pmc))
|
||||
self.assertTrue(inspect.isgenerator(gen_coro))
|
||||
|
||||
async def _fn3():
|
||||
|
@ -285,6 +308,8 @@ class TestPredicates(IsTestBase):
|
|||
inspect.iscoroutinefunction(
|
||||
functools.partial(functools.partial(
|
||||
coroutine_function_example))))
|
||||
self.assertTrue(inspect.iscoroutinefunction(coro_pmi))
|
||||
self.assertTrue(inspect.iscoroutinefunction(coro_pmc))
|
||||
self.assertTrue(inspect.iscoroutine(coro))
|
||||
|
||||
self.assertFalse(
|
||||
|
@ -297,6 +322,8 @@ class TestPredicates(IsTestBase):
|
|||
inspect.isgeneratorfunction(
|
||||
functools.partial(functools.partial(
|
||||
coroutine_function_example))))
|
||||
self.assertFalse(inspect.isgeneratorfunction(coro_pmi))
|
||||
self.assertFalse(inspect.isgeneratorfunction(coro_pmc))
|
||||
self.assertFalse(inspect.isgenerator(coro))
|
||||
|
||||
self.assertFalse(
|
||||
|
@ -311,6 +338,8 @@ class TestPredicates(IsTestBase):
|
|||
inspect.isasyncgenfunction(
|
||||
functools.partial(functools.partial(
|
||||
async_generator_function_example))))
|
||||
self.assertTrue(inspect.isasyncgenfunction(async_gen_coro_pmi))
|
||||
self.assertTrue(inspect.isasyncgenfunction(async_gen_coro_pmc))
|
||||
self.assertTrue(inspect.isasyncgen(async_gen_coro))
|
||||
|
||||
coro.close(); gen_coro.close(); # silence warnings
|
||||
|
@ -3389,7 +3418,7 @@ class TestSignatureObject(unittest.TestCase):
|
|||
|
||||
def test_signature_on_fake_partialmethod(self):
|
||||
def foo(a): pass
|
||||
foo._partialmethod = 'spam'
|
||||
foo.__partialmethod__ = 'spam'
|
||||
self.assertEqual(str(inspect.signature(foo)), '(a)')
|
||||
|
||||
def test_signature_on_decorated(self):
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
The ``inspect`` functions ``isgeneratorfunction``, ``iscoroutinefunction``, ``isasyncgenfunction`` now support ``functools.partialmethod`` wrapped functions the same way they support ``functools.partial``.
|
Loading…
Reference in New Issue