Skip to content

Commit

Permalink
[3.12] gh-112006: Fix inspect.unwrap() for types where __wrapped__ is…
Browse files Browse the repository at this point in the history
… a data descriptor (GH-115540) (GH-115966)

(cherry picked from commit 68c79d2)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
  • Loading branch information
miss-islington and serhiy-storchaka committed Feb 27, 2024
1 parent 3af945f commit 418e720
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 13 deletions.
10 changes: 3 additions & 7 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,18 +760,14 @@ def unwrap(func, *, stop=None):
:exc:`ValueError` is raised if a cycle is encountered.
"""
if stop is None:
def _is_wrapper(f):
return hasattr(f, '__wrapped__')
else:
def _is_wrapper(f):
return hasattr(f, '__wrapped__') and not stop(f)
f = func # remember the original func for error reporting
# Memoise by id to tolerate non-hashable objects, but store objects to
# ensure they aren't destroyed, which would allow their IDs to be reused.
memo = {id(f): f}
recursion_limit = sys.getrecursionlimit()
while _is_wrapper(func):
while not isinstance(func, type) and hasattr(func, '__wrapped__'):
if stop is not None and stop(func):
break
func = func.__wrapped__
id_func = id(func)
if (id_func in memo) or (len(memo) >= recursion_limit):
Expand Down
24 changes: 18 additions & 6 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -3488,16 +3488,20 @@ class Bar(Spam, Foo):
((('a', ..., ..., "positional_or_keyword"),),
...))

class Wrapped:
pass
Wrapped.__wrapped__ = lambda a: None
self.assertEqual(self.signature(Wrapped),
def test_signature_on_wrapper(self):
class Wrapper:
def __call__(self, b):
pass
wrapper = Wrapper()
wrapper.__wrapped__ = lambda a: None
self.assertEqual(self.signature(wrapper),
((('a', ..., ..., "positional_or_keyword"),),
...))
# wrapper loop:
Wrapped.__wrapped__ = Wrapped
wrapper = Wrapper()
wrapper.__wrapped__ = wrapper
with self.assertRaisesRegex(ValueError, 'wrapper loop'):
self.signature(Wrapped)
self.signature(wrapper)

def test_signature_on_lambdas(self):
self.assertEqual(self.signature((lambda a=10: a)),
Expand Down Expand Up @@ -4672,6 +4676,14 @@ def test_recursion_limit(self):
with self.assertRaisesRegex(ValueError, 'wrapper loop'):
inspect.unwrap(obj)

def test_wrapped_descriptor(self):
self.assertIs(inspect.unwrap(NTimesUnwrappable), NTimesUnwrappable)
self.assertIs(inspect.unwrap(staticmethod), staticmethod)
self.assertIs(inspect.unwrap(classmethod), classmethod)
self.assertIs(inspect.unwrap(staticmethod(classmethod)), classmethod)
self.assertIs(inspect.unwrap(classmethod(staticmethod)), staticmethod)


class TestMain(unittest.TestCase):
def test_only_source(self):
module = importlib.import_module('unittest')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :func:`inspect.unwrap` for types with the ``__wrapper__`` data
descriptor.

0 comments on commit 418e720

Please sign in to comment.