Skip to content

Commit a8143e9

Browse files
serhiy-storchakamiss-islington
authored andcommitted
pythongh-112006: Fix inspect.unwrap() for types where __wrapped__ is a data descriptor (pythonGH-115540)
This also fixes inspect.Signature.from_callable() for builtins classmethod() and staticmethod(). (cherry picked from commit 68c79d2) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 53b84e7 commit a8143e9

File tree

3 files changed

+32
-13
lines changed

3 files changed

+32
-13
lines changed

Lib/inspect.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -760,18 +760,14 @@ def unwrap(func, *, stop=None):
760760
:exc:`ValueError` is raised if a cycle is encountered.
761761
762762
"""
763-
if stop is None:
764-
def _is_wrapper(f):
765-
return hasattr(f, '__wrapped__')
766-
else:
767-
def _is_wrapper(f):
768-
return hasattr(f, '__wrapped__') and not stop(f)
769763
f = func # remember the original func for error reporting
770764
# Memoise by id to tolerate non-hashable objects, but store objects to
771765
# ensure they aren't destroyed, which would allow their IDs to be reused.
772766
memo = {id(f): f}
773767
recursion_limit = sys.getrecursionlimit()
774-
while _is_wrapper(func):
768+
while not isinstance(func, type) and hasattr(func, '__wrapped__'):
769+
if stop is not None and stop(func):
770+
break
775771
func = func.__wrapped__
776772
id_func = id(func)
777773
if (id_func in memo) or (len(memo) >= recursion_limit):

Lib/test/test_inspect/test_inspect.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2947,6 +2947,10 @@ def m1d(*args, **kwargs):
29472947
int))
29482948

29492949
def test_signature_on_classmethod(self):
2950+
self.assertEqual(self.signature(classmethod),
2951+
((('function', ..., ..., "positional_only"),),
2952+
...))
2953+
29502954
class Test:
29512955
@classmethod
29522956
def foo(cls, arg1, *, arg2=1):
@@ -2965,6 +2969,10 @@ def foo(cls, arg1, *, arg2=1):
29652969
...))
29662970

29672971
def test_signature_on_staticmethod(self):
2972+
self.assertEqual(self.signature(staticmethod),
2973+
((('function', ..., ..., "positional_only"),),
2974+
...))
2975+
29682976
class Test:
29692977
@staticmethod
29702978
def foo(cls, *, arg):
@@ -3488,16 +3496,20 @@ class Bar(Spam, Foo):
34883496
((('a', ..., ..., "positional_or_keyword"),),
34893497
...))
34903498

3491-
class Wrapped:
3492-
pass
3493-
Wrapped.__wrapped__ = lambda a: None
3494-
self.assertEqual(self.signature(Wrapped),
3499+
def test_signature_on_wrapper(self):
3500+
class Wrapper:
3501+
def __call__(self, b):
3502+
pass
3503+
wrapper = Wrapper()
3504+
wrapper.__wrapped__ = lambda a: None
3505+
self.assertEqual(self.signature(wrapper),
34953506
((('a', ..., ..., "positional_or_keyword"),),
34963507
...))
34973508
# wrapper loop:
3498-
Wrapped.__wrapped__ = Wrapped
3509+
wrapper = Wrapper()
3510+
wrapper.__wrapped__ = wrapper
34993511
with self.assertRaisesRegex(ValueError, 'wrapper loop'):
3500-
self.signature(Wrapped)
3512+
self.signature(wrapper)
35013513

35023514
def test_signature_on_lambdas(self):
35033515
self.assertEqual(self.signature((lambda a=10: a)),
@@ -4672,6 +4684,14 @@ def test_recursion_limit(self):
46724684
with self.assertRaisesRegex(ValueError, 'wrapper loop'):
46734685
inspect.unwrap(obj)
46744686

4687+
def test_wrapped_descriptor(self):
4688+
self.assertIs(inspect.unwrap(NTimesUnwrappable), NTimesUnwrappable)
4689+
self.assertIs(inspect.unwrap(staticmethod), staticmethod)
4690+
self.assertIs(inspect.unwrap(classmethod), classmethod)
4691+
self.assertIs(inspect.unwrap(staticmethod(classmethod)), classmethod)
4692+
self.assertIs(inspect.unwrap(classmethod(staticmethod)), staticmethod)
4693+
4694+
46754695
class TestMain(unittest.TestCase):
46764696
def test_only_source(self):
46774697
module = importlib.import_module('unittest')
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`inspect.unwrap` for types with the ``__wrapper__`` data
2+
descriptor. Fix :meth:`inspect.Signature.from_callable` for builtins
3+
:func:`classmethod` and :func:`staticmethod`.

0 commit comments

Comments
 (0)