Skip to content

Commit 45261e6

Browse files
serhiy-storchakawoodruffw
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().
1 parent f534d7f commit 45261e6

File tree

3 files changed

+32
-13
lines changed

3 files changed

+32
-13
lines changed

Lib/inspect.py

+3-7
Original file line numberDiff line numberDiff line change
@@ -762,18 +762,14 @@ def unwrap(func, *, stop=None):
762762
:exc:`ValueError` is raised if a cycle is encountered.
763763
764764
"""
765-
if stop is None:
766-
def _is_wrapper(f):
767-
return hasattr(f, '__wrapped__')
768-
else:
769-
def _is_wrapper(f):
770-
return hasattr(f, '__wrapped__') and not stop(f)
771765
f = func # remember the original func for error reporting
772766
# Memoise by id to tolerate non-hashable objects, but store objects to
773767
# ensure they aren't destroyed, which would allow their IDs to be reused.
774768
memo = {id(f): f}
775769
recursion_limit = sys.getrecursionlimit()
776-
while _is_wrapper(func):
770+
while not isinstance(func, type) and hasattr(func, '__wrapped__'):
771+
if stop is not None and stop(func):
772+
break
777773
func = func.__wrapped__
778774
id_func = id(func)
779775
if (id_func in memo) or (len(memo) >= recursion_limit):

Lib/test/test_inspect/test_inspect.py

+26-6
Original file line numberDiff line numberDiff line change
@@ -3137,6 +3137,10 @@ def m1d(*args, **kwargs):
31373137
int))
31383138

31393139
def test_signature_on_classmethod(self):
3140+
self.assertEqual(self.signature(classmethod),
3141+
((('function', ..., ..., "positional_only"),),
3142+
...))
3143+
31403144
class Test:
31413145
@classmethod
31423146
def foo(cls, arg1, *, arg2=1):
@@ -3155,6 +3159,10 @@ def foo(cls, arg1, *, arg2=1):
31553159
...))
31563160

31573161
def test_signature_on_staticmethod(self):
3162+
self.assertEqual(self.signature(staticmethod),
3163+
((('function', ..., ..., "positional_only"),),
3164+
...))
3165+
31583166
class Test:
31593167
@staticmethod
31603168
def foo(cls, *, arg):
@@ -3678,16 +3686,20 @@ class Bar(Spam, Foo):
36783686
((('a', ..., ..., "positional_or_keyword"),),
36793687
...))
36803688

3681-
class Wrapped:
3682-
pass
3683-
Wrapped.__wrapped__ = lambda a: None
3684-
self.assertEqual(self.signature(Wrapped),
3689+
def test_signature_on_wrapper(self):
3690+
class Wrapper:
3691+
def __call__(self, b):
3692+
pass
3693+
wrapper = Wrapper()
3694+
wrapper.__wrapped__ = lambda a: None
3695+
self.assertEqual(self.signature(wrapper),
36853696
((('a', ..., ..., "positional_or_keyword"),),
36863697
...))
36873698
# wrapper loop:
3688-
Wrapped.__wrapped__ = Wrapped
3699+
wrapper = Wrapper()
3700+
wrapper.__wrapped__ = wrapper
36893701
with self.assertRaisesRegex(ValueError, 'wrapper loop'):
3690-
self.signature(Wrapped)
3702+
self.signature(wrapper)
36913703

36923704
def test_signature_on_lambdas(self):
36933705
self.assertEqual(self.signature((lambda a=10: a)),
@@ -4999,6 +5011,14 @@ def test_recursion_limit(self):
49995011
with self.assertRaisesRegex(ValueError, 'wrapper loop'):
50005012
inspect.unwrap(obj)
50015013

5014+
def test_wrapped_descriptor(self):
5015+
self.assertIs(inspect.unwrap(NTimesUnwrappable), NTimesUnwrappable)
5016+
self.assertIs(inspect.unwrap(staticmethod), staticmethod)
5017+
self.assertIs(inspect.unwrap(classmethod), classmethod)
5018+
self.assertIs(inspect.unwrap(staticmethod(classmethod)), classmethod)
5019+
self.assertIs(inspect.unwrap(classmethod(staticmethod)), staticmethod)
5020+
5021+
50025022
class TestMain(unittest.TestCase):
50035023
def test_only_source(self):
50045024
module = importlib.import_module('unittest')
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)