From f0efff0b5c01f9813567a9c39e7c3e297894b7d5 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 23 Mar 2025 13:55:14 +0000 Subject: [PATCH 1/5] gh-131628: use dock-typing in `inspect.getfile` --- Lib/inspect.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index fcfe3b191ab5030..275a3de0372b728 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -265,7 +265,7 @@ def isgetsetdescriptor(object): return False def isfunction(object): - """Return true if the object is a user-defined function. + """Return true if the object is a user-defined Python function. Function objects provide these attributes: __doc__ documentation string @@ -828,9 +828,9 @@ def getfile(object): if object.__module__ == '__main__': raise OSError('source code not available') raise TypeError('{!r} is a built-in class'.format(object)) - if ismethod(object): + if hasattr(object, '__func__'): object = object.__func__ - if isfunction(object): + if hasattr(object, '__code__'): object = object.__code__ if istraceback(object): object = object.tb_frame From aa65c2e9e10fc1e22a22b107b6e8a8c247265d28 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 23 Mar 2025 14:04:46 +0000 Subject: [PATCH 2/5] similar changes for findsource --- Lib/inspect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index 275a3de0372b728..8dbde52f89de565 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -988,9 +988,9 @@ def findsource(object): raise OSError('lineno is out of bounds') return lines, lnum - if ismethod(object): + if hasattr(object, '__func__'): object = object.__func__ - if isfunction(object): + if hasattr(object, '__code__'): object = object.__code__ if istraceback(object): object = object.tb_frame From 9bc02cbcff71d94f735750e70d5a05bfde01d71e Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 23 Mar 2025 14:13:21 +0000 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2025-03-23-14-13-20.gh-issue-131628.M9Q06m.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-03-23-14-13-20.gh-issue-131628.M9Q06m.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-23-14-13-20.gh-issue-131628.M9Q06m.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-23-14-13-20.gh-issue-131628.M9Q06m.rst new file mode 100644 index 000000000000000..daeb3b36bcc54e2 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-23-14-13-20.gh-issue-131628.M9Q06m.rst @@ -0,0 +1 @@ +Use dock-typing in ``inspect`` to support Cython functions in ``getfile`` and ``getsource`` From d33632b8c5815659d622e89255e118d04c26ccd7 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 29 Mar 2025 17:35:31 +0100 Subject: [PATCH 4/5] Update Misc/NEWS.d/next/Core_and_Builtins/2025-03-23-14-13-20.gh-issue-131628.M9Q06m.rst Co-authored-by: Nicolas Trangez --- .../2025-03-23-14-13-20.gh-issue-131628.M9Q06m.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-23-14-13-20.gh-issue-131628.M9Q06m.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-23-14-13-20.gh-issue-131628.M9Q06m.rst index daeb3b36bcc54e2..f5409bc66e60e71 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-03-23-14-13-20.gh-issue-131628.M9Q06m.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-03-23-14-13-20.gh-issue-131628.M9Q06m.rst @@ -1 +1 @@ -Use dock-typing in ``inspect`` to support Cython functions in ``getfile`` and ``getsource`` +Use duck-typing in ``inspect`` to support Cython functions in ``getfile`` and ``getsource`` From 0fe9d8bcdab207d062880d29c4cb89743466ba0f Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Mon, 19 Oct 2026 00:24:35 +0200 Subject: [PATCH 5/5] implement reviewer feedback --- Lib/inspect.py | 2 +- Lib/test/test_inspect/inspect_fodder2.py | 30 ++++++++++++++++++++++++ Lib/test/test_inspect/test_inspect.py | 8 +++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index f94ccb906cb0491..2ae62c22efd2ad3 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -270,7 +270,7 @@ def isgetsetdescriptor(object): return False def isfunction(object): - """Return true if the object is a user-defined Python function. + """Return true if the object is a user-defined function. Function objects provide these attributes: __doc__ documentation string diff --git a/Lib/test/test_inspect/inspect_fodder2.py b/Lib/test/test_inspect/inspect_fodder2.py index 157e12167b5d270..656f90a979a2af2 100644 --- a/Lib/test/test_inspect/inspect_fodder2.py +++ b/Lib/test/test_inspect/inspect_fodder2.py @@ -400,4 +400,34 @@ def func394(): def func400(): return 401 +# line 403 +class FunctionLike: + # Like a function compiled by Cython: it provides __code__, but it is not + # an instance of types.FunctionType. It does not set __wrapped__, so + # inspect cannot unwrap it to get back to the original function. + def __init__(self, func): + self.__code__ = func.__code__ + + def __get__(self, instance, owner=None): + if instance is None: + return self + return MethodLike(self, instance) + +class MethodLike: + # Provides __func__, but is not an instance of types.MethodType. + def __init__(self, func, instance): + self.__func__ = func + self.__self__ = instance + +# line 422 +@FunctionLike +def func424(): + return 425 + +# line 427 +class cls428: + @FunctionLike + def func430(self): + return 431 + pass # end of file diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index ff7475447e95a03..fb781a0173a9809 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1039,6 +1039,14 @@ def test_getsource_unwrap(self): def test_decorator_with_lambda(self): self.assertSourceEqual(mod2.func114, 113, 115) + def test_function_like_decorator(self): + # Test duck-typed functions and methods (as dummies for Cython functions) + self.assertEqual(inspect.getfile(mod2.func424), mod2.__file__) + self.assertSourceEqual(mod2.func424, 423, 425) + method = mod2.cls428().func430 + self.assertEqual(inspect.getfile(method), mod2.__file__) + self.assertSourceEqual(method, 429, 431) + class TestOneliners(GetSourceBase): fodderModule = mod2 def test_oneline_lambda(self):