From cbab1ea65218e7bc48e7096ab4ca087463d6529d Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Sat, 16 Aug 2025 13:24:06 +0000 Subject: [PATCH 01/12] fix: unwrap descriptor get result Signed-off-by: GitHub --- Lib/inspect.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index 183e67fabf966e..f2e2cf54c1e90e 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1922,8 +1922,6 @@ def _signature_get_user_defined_method(cls, method_name, *, follow_wrapper_chain return None if method_name != '__new__': meth = _descriptor_get(meth, cls) - if follow_wrapper_chains: - meth = unwrap(meth, stop=lambda m: hasattr(m, "__signature__")) return meth From dca94bef31d57ad7cad061996fe58acee22370b1 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Sat, 16 Aug 2025 15:41:56 +0000 Subject: [PATCH 02/12] test: add decorated tests --- Lib/test/test_inspect/test_inspect.py | 225 +++++++++++++++++++++++--- 1 file changed, 200 insertions(+), 25 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index c8c1a5226114b9..8e9cd5107b0de8 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -4021,44 +4021,219 @@ def __init__(self, b): ('bar', 2, ..., "keyword_only")), ...)) - def test_signature_on_class_with_decorated_new(self): + def test_signature_on_class_with_decorated_init(self): def identity(func): @functools.wraps(func) def wrapped(*args, **kwargs): return func(*args, **kwargs) return wrapped - class Foo: + class C: @identity - def __new__(cls, a, b): + def __init__(self, b): pass - self.assertEqual(self.signature(Foo), - ((('a', ..., ..., "positional_or_keyword"), - ('b', ..., ..., "positional_or_keyword")), - ...)) + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) - self.assertEqual(self.signature(Foo.__new__), - ((('cls', ..., ..., "positional_or_keyword"), - ('a', ..., ..., "positional_or_keyword"), - ('b', ..., ..., "positional_or_keyword")), - ...)) + with self.subTest('classmethod'): + class C: + @classmethod + @identity + def __init__(cls, b): + pass - class Bar: - __new__ = identity(object.__new__) + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) - varargs_signature = ( - (('args', ..., ..., 'var_positional'), - ('kwargs', ..., ..., 'var_keyword')), - ..., - ) + with self.subTest('staticmethod'): + class C: + @staticmethod + @identity + def __init__(b): + pass + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('MethodType'): + class A: + @identity + def call(self, a): + pass + + class C: + __init__ = A().call + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partial'): + class C: + __init__ = identity(functools.partial(lambda x, a, b: None, 2)) + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partialmethod'): + class C: + @identity + def _init(self, x, a): + self.a = (x, a) + __init__ = functools.partialmethod(_init, 2) + + self.assertEqual(C(1).a, (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + class Desc: + def __init__(self, func): + self.func = identity(func) + + def __get__(self, instance, owner): + return self.func.__get__(instance, owner) + + with self.subTest('descriptor'): + class C: + __init__ = Desc(lambda self, a: None) - self.assertEqual(self.signature(Bar), ((), ...)) - self.assertEqual(self.signature(Bar.__new__), varargs_signature) - self.assertEqual(self.signature(Bar, follow_wrapped=False), - varargs_signature) - self.assertEqual(self.signature(Bar.__new__, follow_wrapped=False), - varargs_signature) + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + self.assertEqual(self.signature(C.__init__), + ((('self', ..., ..., "positional_or_keyword"), + ('a', ..., ..., "positional_or_keyword")), + ...)) + + varargs_signature = ( + (('args', ..., ..., 'var_positional'), + ('kwargs', ..., ..., 'var_keyword')), + ..., + ) + self.assertEqual(self.signature(C, follow_wrapped=False), + varargs_signature) + self.assertEqual(self.signature(C.__new__, follow_wrapped=False), + varargs_signature) + + + def test_signature_on_class_with_decorated_new(self): + def identity(func): + @functools.wraps(func) + def wrapped(*args, **kwargs): + return func(*args, **kwargs) + return wrapped + + with self.subTest('FunctionType'): + class C: + @identity + def __new__(cls, a): + return a + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + # TODO: classmethod is not correct + # with self.subTest('classmethod'): + # class C: + # @classmethod + # @identity + # def __new__(cls, cls2, a): + # return a + + # self.assertEqual(C(1), 1) + # self.assertEqual(self.signature(C), + # ((('a', ..., ..., "positional_or_keyword"),), + # ...)) + + with self.subTest('staticmethod'): + class C: + @staticmethod + @identity + def __new__(cls, a): + return a + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + # TODO: method type is not correct + # with self.subTest('MethodType'): + # class A: + # @identity + # def call(self, cls, a): + # return a + # class C: + # __new__ = A().call + + # self.assertEqual(C(1), 1) + # self.assertEqual(self.signature(C), + # ((('a', ..., ..., "positional_or_keyword"),), + # ...)) + + with self.subTest('partial'): + class C: + __new__ = identity(functools.partial(lambda x, cls, a: (x, a), 2)) + + self.assertEqual(C(1), (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partialmethod'): + class C: + __new__ = functools.partialmethod(identity(lambda cls, x, a: (x, a)), 2) + + self.assertEqual(C(1), (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + class Desc: + def __init__(self, func): + self.func = identity(func) + + def __get__(self, instance, owner): + return self.func.__get__(instance, owner) + + with self.subTest('descriptor'): + class C: + __new__ = Desc(lambda cls, a: a) + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + self.assertEqual(self.signature(C.__new__), + ((('cls', ..., ..., "positional_or_keyword"), + ('a', ..., ..., "positional_or_keyword")), + ...)) + + varargs_signature = ( + (('args', ..., ..., 'var_positional'), + ('kwargs', ..., ..., 'var_keyword')), + ..., + ) + self.assertEqual(self.signature(C, follow_wrapped=False), + varargs_signature) + self.assertEqual(self.signature(C.__new__, follow_wrapped=False), + varargs_signature) def test_signature_on_class_with_init(self): class C: From 9541a02832a5c0a4b16752a7893bac59b501d897 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Sat, 16 Aug 2025 16:04:58 +0000 Subject: [PATCH 03/12] chore: add news entry --- .../next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst diff --git a/Misc/NEWS.d/next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst b/Misc/NEWS.d/next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst new file mode 100644 index 00000000000000..050a252f3683db --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst @@ -0,0 +1,2 @@ +Fix the behavior when using :func:`inspect.signature` to get the signature +for a class that uses descriptor on a wrapped :meth:`!__init__` methods. From c5b53dcdbbe72bbb563524eb238ec27500407061 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Wed, 27 Aug 2025 05:29:51 +0000 Subject: [PATCH 04/12] fix: classmethod __new__ --- Lib/inspect.py | 6 ++-- Lib/test/test_inspect/test_inspect.py | 46 +++++++++++++-------------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index fbc418193e1f3a..c1f0cb28d12388 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1917,10 +1917,12 @@ def _signature_get_user_defined_method(cls, method_name, *, follow_wrapper_chain if meth is None: return None + unwrapped_meth = None if follow_wrapper_chains: - meth = unwrap(meth, stop=(lambda m: hasattr(m, "__signature__") + unwrapped_meth = unwrap(meth, stop=(lambda m: hasattr(m, "__signature__") or _signature_is_builtin(m))) - if isinstance(meth, _NonUserDefinedCallables): + if (isinstance(meth, _NonUserDefinedCallables) + or isinstance(unwrapped_meth, _NonUserDefinedCallables)): # Once '__signature__' will be added to 'C'-level # callables, this check won't be necessary return None diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 752d3b4316d6fb..8dfc7ec7d1a35e 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -4154,17 +4154,17 @@ def __new__(cls, a): ...)) # TODO: classmethod is not correct - # with self.subTest('classmethod'): - # class C: - # @classmethod - # @identity - # def __new__(cls, cls2, a): - # return a - - # self.assertEqual(C(1), 1) - # self.assertEqual(self.signature(C), - # ((('a', ..., ..., "positional_or_keyword"),), - # ...)) + with self.subTest('classmethod'): + class C: + @classmethod + @identity + def __new__(cls, cls2, a): + return a + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) with self.subTest('staticmethod'): class C: @@ -4179,18 +4179,18 @@ def __new__(cls, a): ...)) # TODO: method type is not correct - # with self.subTest('MethodType'): - # class A: - # @identity - # def call(self, cls, a): - # return a - # class C: - # __new__ = A().call - - # self.assertEqual(C(1), 1) - # self.assertEqual(self.signature(C), - # ((('a', ..., ..., "positional_or_keyword"),), - # ...)) + with self.subTest('MethodType'): + class A: + @identity + def call(self, cls, a): + return a + class C: + __new__ = A().call + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) with self.subTest('partial'): class C: From 8a4fb3055a20e8436e0f3d69ba393b25ee664521 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Wed, 27 Aug 2025 05:52:54 +0000 Subject: [PATCH 05/12] chore: update news and add comment --- Lib/inspect.py | 4 ++++ .../Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index c1f0cb28d12388..5a46987b78b437 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1917,10 +1917,14 @@ def _signature_get_user_defined_method(cls, method_name, *, follow_wrapper_chain if meth is None: return None + # NOTE: The meth may wraps a non-user-defined callable. + # In this case, we treat the meth as non-user-defined callable too. + # (e.g. cls.__new__ generated by @warnings.deprecated) unwrapped_meth = None if follow_wrapper_chains: unwrapped_meth = unwrap(meth, stop=(lambda m: hasattr(m, "__signature__") or _signature_is_builtin(m))) + if (isinstance(meth, _NonUserDefinedCallables) or isinstance(unwrapped_meth, _NonUserDefinedCallables)): # Once '__signature__' will be added to 'C'-level diff --git a/Misc/NEWS.d/next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst b/Misc/NEWS.d/next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst index 050a252f3683db..6359eef7ab32a0 100644 --- a/Misc/NEWS.d/next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst +++ b/Misc/NEWS.d/next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst @@ -1,2 +1,2 @@ -Fix the behavior when using :func:`inspect.signature` to get the signature -for a class that uses descriptor on a wrapped :meth:`!__init__` methods. +:func:`inspect.signature` now correctly handles classes that use a descriptor +on a wrapped :meth:`!__init__` or :meth:`!__new__` method. From 889e9ec58845a3d3e1e8f1b5e47f6c346a39fcf0 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Wed, 27 Aug 2025 14:07:46 +0800 Subject: [PATCH 06/12] chore: update news --- .../next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/NEWS.d/next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst b/Misc/NEWS.d/next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst index 6359eef7ab32a0..026cc320455963 100644 --- a/Misc/NEWS.d/next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst +++ b/Misc/NEWS.d/next/Library/2025-08-16-16-04-15.gh-issue-137317.Dl13B5.rst @@ -1,2 +1,3 @@ :func:`inspect.signature` now correctly handles classes that use a descriptor on a wrapped :meth:`!__init__` or :meth:`!__new__` method. +Contributed by Yongyu Yan. From 3087840902791877fc0192bb3cf34d59709c25e9 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Wed, 27 Aug 2025 14:11:12 +0800 Subject: [PATCH 07/12] chore: remove unused comment --- Lib/test/test_inspect/test_inspect.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 8dfc7ec7d1a35e..769c0c3c93e83c 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -4153,7 +4153,6 @@ def __new__(cls, a): ((('a', ..., ..., "positional_or_keyword"),), ...)) - # TODO: classmethod is not correct with self.subTest('classmethod'): class C: @classmethod @@ -4178,7 +4177,6 @@ def __new__(cls, a): ((('a', ..., ..., "positional_or_keyword"),), ...)) - # TODO: method type is not correct with self.subTest('MethodType'): class A: @identity From 67dc247e52adaebac860e21b19a920cc0c12baab Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Thu, 28 Aug 2025 04:36:07 +0000 Subject: [PATCH 08/12] test: update test cases --- Lib/test/test_inspect/test_inspect.py | 43 ++++++++++++--------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 769c0c3c93e83c..c947d55b147df8 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -148,6 +148,21 @@ def meth_self_o(self, object, /): pass def meth_type_noargs(type, /): pass def meth_type_o(type, object, /): pass +# Simple decorator for test cases that using decorated functions +def identity(func): + @functools.wraps(func) + def wrapped(*args, **kwargs): + return func(*args, **kwargs) + return wrapped + +# Simple descriptor that decorates a function +class DescWithDeco: + def __init__(self, func): + self.func = identity(func) + + def __get__(self, instance, owner): + return self.func.__get__(instance, owner) + class TestPredicates(IsTestBase): @@ -4028,12 +4043,6 @@ def __init__(self, b): ...)) def test_signature_on_class_with_decorated_init(self): - def identity(func): - @functools.wraps(func) - def wrapped(*args, **kwargs): - return func(*args, **kwargs) - return wrapped - class C: @identity def __init__(self, b): @@ -4084,7 +4093,7 @@ class C: with self.subTest('partial'): class C: - __init__ = identity(functools.partial(lambda x, a, b: None, 2)) + __init__ = functools.partial(identity(lambda x, a, b: None), 2) C(1) # does not raise self.assertEqual(self.signature(C), @@ -4103,16 +4112,9 @@ def _init(self, x, a): ((('a', ..., ..., "positional_or_keyword"),), ...)) - class Desc: - def __init__(self, func): - self.func = identity(func) - - def __get__(self, instance, owner): - return self.func.__get__(instance, owner) - with self.subTest('descriptor'): class C: - __init__ = Desc(lambda self, a: None) + __init__ = DescWithDeco(lambda self, a: None) C(1) # does not raise self.assertEqual(self.signature(C), @@ -4192,7 +4194,7 @@ class C: with self.subTest('partial'): class C: - __new__ = identity(functools.partial(lambda x, cls, a: (x, a), 2)) + __new__ = functools.partial(identity(lambda x, cls, a: (x, a)), 2) self.assertEqual(C(1), (2, 1)) self.assertEqual(self.signature(C), @@ -4208,16 +4210,9 @@ class C: ((('a', ..., ..., "positional_or_keyword"),), ...)) - class Desc: - def __init__(self, func): - self.func = identity(func) - - def __get__(self, instance, owner): - return self.func.__get__(instance, owner) - with self.subTest('descriptor'): class C: - __new__ = Desc(lambda cls, a: a) + __new__ = DescWithDeco(lambda cls, a: a) self.assertEqual(C(1), 1) self.assertEqual(self.signature(C), From e1c11ec692266113e9c0a9efdfd1a4433ab4d2e0 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:36:03 +0800 Subject: [PATCH 09/12] chore: use deco syntax --- Lib/test/test_inspect/test_inspect.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index c947d55b147df8..b7ac68d6f58ff8 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -4114,7 +4114,9 @@ def _init(self, x, a): with self.subTest('descriptor'): class C: - __init__ = DescWithDeco(lambda self, a: None) + @DescWithDeco + def __init__(self, a): + pass C(1) # does not raise self.assertEqual(self.signature(C), @@ -4212,7 +4214,9 @@ class C: with self.subTest('descriptor'): class C: - __new__ = DescWithDeco(lambda cls, a: a) + @DescWithDeco + def __new__(cls, a): + return a self.assertEqual(C(1), 1) self.assertEqual(self.signature(C), From 43cc52fc0637aea1e125be01970ff7e91318d5f9 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 28 Aug 2025 15:30:25 +0300 Subject: [PATCH 10/12] Update Lib/test/test_inspect/test_inspect.py --- Lib/test/test_inspect/test_inspect.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index b7ac68d6f58ff8..9459629d061efa 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -4140,12 +4140,6 @@ def __init__(self, a): def test_signature_on_class_with_decorated_new(self): - def identity(func): - @functools.wraps(func) - def wrapped(*args, **kwargs): - return func(*args, **kwargs) - return wrapped - with self.subTest('FunctionType'): class C: @identity From 0e63bd538517a7dc37a55418ed35ffeb5e81eaf5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 28 Aug 2025 16:25:56 +0300 Subject: [PATCH 11/12] Add tests for wrapped metaclass __call__ --- Lib/test/test_inspect/test_inspect.py | 73 +++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 9459629d061efa..ce7363e4ef645d 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -4042,6 +4042,79 @@ def __init__(self, b): ('bar', 2, ..., "keyword_only")), ...)) + def test_signature_on_class_with_wrapped_metaclass_call(self): + class CM(type): + @identity + def __call__(cls, a): + pass + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('classmethod'): + class CM(type): + @classmethod + @identity + def __call__(cls, a): + return a + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('staticmethod'): + class CM(type): + @staticmethod + @identity + def __call__(a): + return a + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('MethodType'): + class A: + @identity + def call(self, a): + return a + class CM(type): + __call__ = A().call + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('descriptor'): + class CM(type): + @DescWithDeco + def __call__(self, a): + return a + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + def test_signature_on_class_with_decorated_init(self): class C: @identity From 8c1f6efd7d867be580c7885939ec0a375386b6dc Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 28 Aug 2025 16:58:15 +0300 Subject: [PATCH 12/12] Renaming. --- Lib/test/test_inspect/test_inspect.py | 84 ++++++++++++++------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index ce7363e4ef645d..88fa4b7460c412 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -148,17 +148,25 @@ def meth_self_o(self, object, /): pass def meth_type_noargs(type, /): pass def meth_type_o(type, object, /): pass -# Simple decorator for test cases that using decorated functions -def identity(func): +# Decorator decorator that returns a simple wrapped function +def identity_wrapper(func): @functools.wraps(func) def wrapped(*args, **kwargs): return func(*args, **kwargs) return wrapped -# Simple descriptor that decorates a function -class DescWithDeco: +# Original signature of the simple wrapped function returned by +# identity_wrapper(). +varargs_signature = ( + (('args', ..., ..., 'var_positional'), + ('kwargs', ..., ..., 'var_keyword')), + ..., +) + +# Decorator decorator that returns a simple descriptor +class custom_descriptor: def __init__(self, func): - self.func = identity(func) + self.func = func def __get__(self, instance, owner): return self.func.__get__(instance, owner) @@ -4044,7 +4052,7 @@ def __init__(self, b): def test_signature_on_class_with_wrapped_metaclass_call(self): class CM(type): - @identity + @identity_wrapper def __call__(cls, a): pass class C(metaclass=CM): @@ -4058,7 +4066,7 @@ def __init__(self, b): with self.subTest('classmethod'): class CM(type): @classmethod - @identity + @identity_wrapper def __call__(cls, a): return a class C(metaclass=CM): @@ -4073,7 +4081,7 @@ def __init__(self, b): with self.subTest('staticmethod'): class CM(type): @staticmethod - @identity + @identity_wrapper def __call__(a): return a class C(metaclass=CM): @@ -4087,7 +4095,7 @@ def __init__(self, b): with self.subTest('MethodType'): class A: - @identity + @identity_wrapper def call(self, a): return a class CM(type): @@ -4103,7 +4111,8 @@ def __init__(self, b): with self.subTest('descriptor'): class CM(type): - @DescWithDeco + @custom_descriptor + @identity_wrapper def __call__(self, a): return a class C(metaclass=CM): @@ -4114,10 +4123,18 @@ def __init__(self, b): self.assertEqual(self.signature(C), ((('a', ..., ..., "positional_or_keyword"),), ...)) + self.assertEqual(self.signature(C.__call__), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + self.assertEqual(self.signature(C, follow_wrapped=False), + varargs_signature) + self.assertEqual(self.signature(C.__call__, follow_wrapped=False), + varargs_signature) - def test_signature_on_class_with_decorated_init(self): + def test_signature_on_class_with_wrapped_init(self): class C: - @identity + @identity_wrapper def __init__(self, b): pass @@ -4129,7 +4146,7 @@ def __init__(self, b): with self.subTest('classmethod'): class C: @classmethod - @identity + @identity_wrapper def __init__(cls, b): pass @@ -4141,7 +4158,7 @@ def __init__(cls, b): with self.subTest('staticmethod'): class C: @staticmethod - @identity + @identity_wrapper def __init__(b): pass @@ -4152,7 +4169,7 @@ def __init__(b): with self.subTest('MethodType'): class A: - @identity + @identity_wrapper def call(self, a): pass @@ -4166,7 +4183,7 @@ class C: with self.subTest('partial'): class C: - __init__ = functools.partial(identity(lambda x, a, b: None), 2) + __init__ = functools.partial(identity_wrapper(lambda x, a, b: None), 2) C(1) # does not raise self.assertEqual(self.signature(C), @@ -4175,7 +4192,7 @@ class C: with self.subTest('partialmethod'): class C: - @identity + @identity_wrapper def _init(self, x, a): self.a = (x, a) __init__ = functools.partialmethod(_init, 2) @@ -4187,7 +4204,8 @@ def _init(self, x, a): with self.subTest('descriptor'): class C: - @DescWithDeco + @custom_descriptor + @identity_wrapper def __init__(self, a): pass @@ -4195,27 +4213,20 @@ def __init__(self, a): self.assertEqual(self.signature(C), ((('a', ..., ..., "positional_or_keyword"),), ...)) - self.assertEqual(self.signature(C.__init__), ((('self', ..., ..., "positional_or_keyword"), ('a', ..., ..., "positional_or_keyword")), ...)) - varargs_signature = ( - (('args', ..., ..., 'var_positional'), - ('kwargs', ..., ..., 'var_keyword')), - ..., - ) self.assertEqual(self.signature(C, follow_wrapped=False), varargs_signature) self.assertEqual(self.signature(C.__new__, follow_wrapped=False), varargs_signature) - - def test_signature_on_class_with_decorated_new(self): + def test_signature_on_class_with_wrapped_new(self): with self.subTest('FunctionType'): class C: - @identity + @identity_wrapper def __new__(cls, a): return a @@ -4227,7 +4238,7 @@ def __new__(cls, a): with self.subTest('classmethod'): class C: @classmethod - @identity + @identity_wrapper def __new__(cls, cls2, a): return a @@ -4239,7 +4250,7 @@ def __new__(cls, cls2, a): with self.subTest('staticmethod'): class C: @staticmethod - @identity + @identity_wrapper def __new__(cls, a): return a @@ -4250,7 +4261,7 @@ def __new__(cls, a): with self.subTest('MethodType'): class A: - @identity + @identity_wrapper def call(self, cls, a): return a class C: @@ -4263,7 +4274,7 @@ class C: with self.subTest('partial'): class C: - __new__ = functools.partial(identity(lambda x, cls, a: (x, a)), 2) + __new__ = functools.partial(identity_wrapper(lambda x, cls, a: (x, a)), 2) self.assertEqual(C(1), (2, 1)) self.assertEqual(self.signature(C), @@ -4272,7 +4283,7 @@ class C: with self.subTest('partialmethod'): class C: - __new__ = functools.partialmethod(identity(lambda cls, x, a: (x, a)), 2) + __new__ = functools.partialmethod(identity_wrapper(lambda cls, x, a: (x, a)), 2) self.assertEqual(C(1), (2, 1)) self.assertEqual(self.signature(C), @@ -4281,7 +4292,8 @@ class C: with self.subTest('descriptor'): class C: - @DescWithDeco + @custom_descriptor + @identity_wrapper def __new__(cls, a): return a @@ -4289,17 +4301,11 @@ def __new__(cls, a): self.assertEqual(self.signature(C), ((('a', ..., ..., "positional_or_keyword"),), ...)) - self.assertEqual(self.signature(C.__new__), ((('cls', ..., ..., "positional_or_keyword"), ('a', ..., ..., "positional_or_keyword")), ...)) - varargs_signature = ( - (('args', ..., ..., 'var_positional'), - ('kwargs', ..., ..., 'var_keyword')), - ..., - ) self.assertEqual(self.signature(C, follow_wrapped=False), varargs_signature) self.assertEqual(self.signature(C.__new__, follow_wrapped=False),