diff --git a/Lib/inspect.py b/Lib/inspect.py index 90c44cf74007a8..262efcbfcccbf1 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -804,6 +804,23 @@ def cleandoc(doc): return '\n'.join(lines) +def _get_code_object(obj): + """Walk through a callable or frame to find the code object""" + if ismethod(obj): + obj = obj.__func__ + if isfunction(obj): + obj = unwrap(obj) + if isfunction(obj): + obj = obj.__code__ + if istraceback(obj): + obj = obj.tb_frame + if isframe(obj): + obj = obj.f_code + if iscode(obj): + return obj + raise TypeError + + def getfile(object): """Work out which source or compiled file an object was defined in.""" if ismodule(object): @@ -818,19 +835,13 @@ 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): - object = object.__func__ - if isfunction(object): - object = object.__code__ - if istraceback(object): - object = object.tb_frame - if isframe(object): - object = object.f_code - if iscode(object): - return object.co_filename - raise TypeError('module, class, method, function, traceback, frame, or ' - 'code object was expected, got {}'.format( - type(object).__name__)) + + try: + return _get_code_object(object).co_filename + except TypeError: + raise TypeError('module, class, method, function, traceback, frame, or ' + 'code object was expected, got {}'.format( + type(object).__name__)) from None def getmodulename(path): """Return the module name for a given file, or None.""" @@ -975,22 +986,17 @@ def findsource(object): raise OSError('source code not available') return lines, firstlineno - 1 - if ismethod(object): - object = object.__func__ - if isfunction(object): - object = object.__code__ - if istraceback(object): - object = object.tb_frame - if isframe(object): - object = object.f_code - if iscode(object): - if not hasattr(object, 'co_firstlineno'): - raise OSError('could not find function definition') - lnum = object.co_firstlineno - 1 - if lnum >= len(lines): - raise OSError('lineno is out of bounds') - return lines, lnum - raise OSError('could not find code object') + try: + object = _get_code_object(object) + except TypeError: + raise OSError('could not find code object') from None + + if not hasattr(object, 'co_firstlineno'): + raise OSError('could not find function definition') + lnum = object.co_firstlineno - 1 + if lnum >= len(lines): + raise OSError('lineno is out of bounds') + return lines, lnum def getcomments(object): """Get lines of comments immediately preceding an object's source code. @@ -1120,7 +1126,6 @@ def getsourcelines(object): corresponding to the object and the line number indicates where in the original source file the first line of code was found. An OSError is raised if the source code cannot be retrieved.""" - object = unwrap(object) lines, lnum = findsource(object) if istraceback(object): diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 81188ad4d1fbe1..85908704dafd3f 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -868,6 +868,12 @@ def test_replacing_decorator(self): def test_getsource_unwrap(self): self.assertSourceEqual(mod2.real, 130, 132) + def test_getcomments_unwrap(self): + self.assertEqual(inspect.getcomments(mod2.real), '#line 129\n') + + def test_getsourcefile_unwrap(self): + self.assertEqual(inspect.getsourcefile(mod2.real), mod2.__file__) + def test_decorator_with_lambda(self): self.assertSourceEqual(mod2.func114, 113, 115) diff --git a/Misc/NEWS.d/next/Library/2018-08-01-23-31-12.bpo-34305.CjGWSo.rst b/Misc/NEWS.d/next/Library/2018-08-01-23-31-12.bpo-34305.CjGWSo.rst new file mode 100644 index 00000000000000..2329e9d5a6a19d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-08-01-23-31-12.bpo-34305.CjGWSo.rst @@ -0,0 +1,3 @@ +Decorators are now unwrapped in :func:`inspect.getcomments`, :func:`inspect.getfile`, +and :func:`inspect.getsourcefile`, making these functions consistent with +:func:`inspect.getsource`.