summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorMartijn Pieters <mj@zopatista.com>2024-02-15 11:08:45 (GMT)
committerGitHub <noreply@github.com>2024-02-15 11:08:45 (GMT)
commitedb59d57188e5535729c3948d673d0de1b729c05 (patch)
tree8de60290f8eda9ffc8dd5e060966218c7c5aed4e /Lib
parent9e3729bbd77fb9dcaea6a06ac760160136d80b79 (diff)
downloadcpython-edb59d57188e5535729c3948d673d0de1b729c05.zip
cpython-edb59d57188e5535729c3948d673d0de1b729c05.tar.gz
cpython-edb59d57188e5535729c3948d673d0de1b729c05.tar.bz2
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>
Diffstat (limited to 'Lib')
-rw-r--r--Lib/functools.py13
-rw-r--r--Lib/inspect.py6
-rw-r--r--Lib/test/test_inspect/test_inspect.py31
3 files changed, 46 insertions, 4 deletions
diff --git a/Lib/functools.py b/Lib/functools.py
index 55990e7..ee4197b 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -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
################################################################################
diff --git a/Lib/inspect.py b/Lib/inspect.py
index f0b7266..450093a 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -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:
diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py
index 4611f62..c5a6de5 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -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):