From 1600f552773bbf46829c4b3a5366cab469891448 Mon Sep 17 00:00:00 2001 From: Anton Ryzhov Date: Thu, 10 Nov 2022 13:32:01 +0100 Subject: [PATCH] gh-74044: inspect.signature for wrappers around decorated bound methods (GH-736) (cherry picked from commit dbf2faf579b4094387d65ee41f049456ca67c446) Co-authored-by: Anton Ryzhov --- Lib/inspect.py | 5 ++++- Lib/test/test_inspect.py | 9 +++++++-- .../2022-11-09-20-48-42.gh-issue-74044.zBj26K.rst | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-11-09-20-48-42.gh-issue-74044.zBj26K.rst diff --git a/Lib/inspect.py b/Lib/inspect.py index 60740c63b2ae19..ad16f5c46e3b9d 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2406,7 +2406,10 @@ def _signature_from_callable(obj, *, # Was this function wrapped by a decorator? if follow_wrapper_chains: - obj = unwrap(obj, stop=(lambda f: hasattr(f, "__signature__"))) + # Unwrap until we find an explicit signature or a MethodType (which will be + # handled explicitly below). + obj = unwrap(obj, stop=(lambda f: hasattr(f, "__signature__") + or isinstance(f, types.MethodType))) if isinstance(obj, types.MethodType): # If the unwrapped object is a *method*, we might want to # skip its first parameter (self). diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 16fef5cab18d2a..03cb3bdddc2c47 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -2957,8 +2957,6 @@ def foo(a): pass self.assertEqual(str(inspect.signature(foo)), '(a)') def test_signature_on_decorated(self): - import functools - def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs) -> int: @@ -2970,6 +2968,8 @@ class Foo: def bar(self, a, b): pass + bar = decorator(Foo().bar) + self.assertEqual(self.signature(Foo.bar), ((('self', ..., ..., "positional_or_keyword"), ('a', ..., ..., "positional_or_keyword"), @@ -2988,6 +2988,11 @@ def bar(self, a, b): # from "func" to "wrapper", hence no # return_annotation + self.assertEqual(self.signature(bar), + ((('a', ..., ..., "positional_or_keyword"), + ('b', ..., ..., "positional_or_keyword")), + ...)) + # Test that we handle method wrappers correctly def decorator(func): @functools.wraps(func) diff --git a/Misc/NEWS.d/next/Library/2022-11-09-20-48-42.gh-issue-74044.zBj26K.rst b/Misc/NEWS.d/next/Library/2022-11-09-20-48-42.gh-issue-74044.zBj26K.rst new file mode 100644 index 00000000000000..3102ef41f1621f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-11-09-20-48-42.gh-issue-74044.zBj26K.rst @@ -0,0 +1 @@ +Fixed bug where :func:`inspect.signature` reported incorrect arguments for decorated methods.