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
|
Functions wrapped in :func:`functools.partial` now return ``True`` if the
|
||||||
wrapped function is a Python generator function.
|
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)
|
.. function:: isgenerator(object)
|
||||||
|
|
||||||
|
@ -363,6 +366,10 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
|
||||||
Sync functions marked with :func:`markcoroutinefunction` now return
|
Sync functions marked with :func:`markcoroutinefunction` now return
|
||||||
``True``.
|
``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)
|
.. 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
|
Functions wrapped in :func:`functools.partial` now return ``True`` if the
|
||||||
wrapped function is a :term:`asynchronous generator` function.
|
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)
|
.. function:: isasyncgen(object)
|
||||||
|
|
||||||
|
|
|
@ -388,7 +388,7 @@ class partialmethod(object):
|
||||||
keywords = {**self.keywords, **keywords}
|
keywords = {**self.keywords, **keywords}
|
||||||
return self.func(cls_or_self, *self.args, *args, **keywords)
|
return self.func(cls_or_self, *self.args, *args, **keywords)
|
||||||
_method.__isabstractmethod__ = self.__isabstractmethod__
|
_method.__isabstractmethod__ = self.__isabstractmethod__
|
||||||
_method._partialmethod = self
|
_method.__partialmethod__ = self
|
||||||
return _method
|
return _method
|
||||||
|
|
||||||
def __get__(self, obj, cls=None):
|
def __get__(self, obj, cls=None):
|
||||||
|
@ -424,6 +424,17 @@ def _unwrap_partial(func):
|
||||||
func = func.func
|
func = func.func
|
||||||
return 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
|
### LRU Cache function decorator
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
|
@ -383,8 +383,10 @@ def isfunction(object):
|
||||||
|
|
||||||
def _has_code_flag(f, flag):
|
def _has_code_flag(f, flag):
|
||||||
"""Return true if ``f`` is a function (or a method or functools.partial
|
"""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."""
|
set in its flags."""
|
||||||
|
f = functools._unwrap_partialmethod(f)
|
||||||
while ismethod(f):
|
while ismethod(f):
|
||||||
f = f.__func__
|
f = f.__func__
|
||||||
f = functools._unwrap_partial(f)
|
f = functools._unwrap_partial(f)
|
||||||
|
@ -2561,7 +2563,7 @@ def _signature_from_callable(obj, *,
|
||||||
return sig
|
return sig
|
||||||
|
|
||||||
try:
|
try:
|
||||||
partialmethod = obj._partialmethod
|
partialmethod = obj.__partialmethod__
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -206,12 +206,33 @@ class TestPredicates(IsTestBase):
|
||||||
gen_coro = gen_coroutine_function_example(1)
|
gen_coro = gen_coroutine_function_example(1)
|
||||||
coro = 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(
|
self.assertFalse(
|
||||||
inspect.iscoroutinefunction(gen_coroutine_function_example))
|
inspect.iscoroutinefunction(gen_coroutine_function_example))
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
inspect.iscoroutinefunction(
|
inspect.iscoroutinefunction(
|
||||||
functools.partial(functools.partial(
|
functools.partial(functools.partial(
|
||||||
gen_coroutine_function_example))))
|
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.assertFalse(inspect.iscoroutine(gen_coro))
|
||||||
|
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
|
@ -220,6 +241,8 @@ class TestPredicates(IsTestBase):
|
||||||
inspect.isgeneratorfunction(
|
inspect.isgeneratorfunction(
|
||||||
functools.partial(functools.partial(
|
functools.partial(functools.partial(
|
||||||
gen_coroutine_function_example))))
|
gen_coroutine_function_example))))
|
||||||
|
self.assertTrue(inspect.isgeneratorfunction(gen_coro_pmi))
|
||||||
|
self.assertTrue(inspect.isgeneratorfunction(gen_coro_pmc))
|
||||||
self.assertTrue(inspect.isgenerator(gen_coro))
|
self.assertTrue(inspect.isgenerator(gen_coro))
|
||||||
|
|
||||||
async def _fn3():
|
async def _fn3():
|
||||||
|
@ -285,6 +308,8 @@ class TestPredicates(IsTestBase):
|
||||||
inspect.iscoroutinefunction(
|
inspect.iscoroutinefunction(
|
||||||
functools.partial(functools.partial(
|
functools.partial(functools.partial(
|
||||||
coroutine_function_example))))
|
coroutine_function_example))))
|
||||||
|
self.assertTrue(inspect.iscoroutinefunction(coro_pmi))
|
||||||
|
self.assertTrue(inspect.iscoroutinefunction(coro_pmc))
|
||||||
self.assertTrue(inspect.iscoroutine(coro))
|
self.assertTrue(inspect.iscoroutine(coro))
|
||||||
|
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
|
@ -297,6 +322,8 @@ class TestPredicates(IsTestBase):
|
||||||
inspect.isgeneratorfunction(
|
inspect.isgeneratorfunction(
|
||||||
functools.partial(functools.partial(
|
functools.partial(functools.partial(
|
||||||
coroutine_function_example))))
|
coroutine_function_example))))
|
||||||
|
self.assertFalse(inspect.isgeneratorfunction(coro_pmi))
|
||||||
|
self.assertFalse(inspect.isgeneratorfunction(coro_pmc))
|
||||||
self.assertFalse(inspect.isgenerator(coro))
|
self.assertFalse(inspect.isgenerator(coro))
|
||||||
|
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
|
@ -311,6 +338,8 @@ class TestPredicates(IsTestBase):
|
||||||
inspect.isasyncgenfunction(
|
inspect.isasyncgenfunction(
|
||||||
functools.partial(functools.partial(
|
functools.partial(functools.partial(
|
||||||
async_generator_function_example))))
|
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))
|
self.assertTrue(inspect.isasyncgen(async_gen_coro))
|
||||||
|
|
||||||
coro.close(); gen_coro.close(); # silence warnings
|
coro.close(); gen_coro.close(); # silence warnings
|
||||||
|
@ -3389,7 +3418,7 @@ class TestSignatureObject(unittest.TestCase):
|
||||||
|
|
||||||
def test_signature_on_fake_partialmethod(self):
|
def test_signature_on_fake_partialmethod(self):
|
||||||
def foo(a): pass
|
def foo(a): pass
|
||||||
foo._partialmethod = 'spam'
|
foo.__partialmethod__ = 'spam'
|
||||||
self.assertEqual(str(inspect.signature(foo)), '(a)')
|
self.assertEqual(str(inspect.signature(foo)), '(a)')
|
||||||
|
|
||||||
def test_signature_on_decorated(self):
|
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