diff --git a/Lib/inspect.py b/Lib/inspect.py index bceed35a53abba1..bdbc671b48b7eb0 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -862,9 +862,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 @@ -1022,9 +1022,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 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 3f14a9e66226eaa..a98346f8765a588 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): 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..f5409bc66e60e71 --- /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 duck-typing in ``inspect`` to support Cython functions in ``getfile`` and ``getsource``