Skip to content

Commit d874835

Browse files
committed
bpo-34305: Unwrap decorators in getfile, rather than downstream
Among other functions, this fixes getsourcefile and getcomments. Removes some code duplication while I'm here
1 parent c1e46e9 commit d874835

File tree

3 files changed

+44
-32
lines changed

3 files changed

+44
-32
lines changed

Lib/inspect.py

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,21 @@ def cleandoc(doc):
639639
lines.pop(0)
640640
return '\n'.join(lines)
641641

642+
def _get_code_object(obj):
643+
"""Walk through a callable or frame to find the code object"""
644+
if ismethod(obj):
645+
obj = obj.__func__
646+
if isfunction(obj):
647+
obj = unwrap(obj)
648+
obj = obj.__code__
649+
if istraceback(obj):
650+
obj = obj.tb_frame
651+
if isframe(obj):
652+
obj = obj.f_code
653+
if iscode(obj):
654+
return obj
655+
raise TypeError
656+
642657
def getfile(object):
643658
"""Work out which source or compiled file an object was defined in."""
644659
if ismodule(object):
@@ -651,19 +666,13 @@ def getfile(object):
651666
if hasattr(object, '__file__'):
652667
return object.__file__
653668
raise TypeError('{!r} is a built-in class'.format(object))
654-
if ismethod(object):
655-
object = object.__func__
656-
if isfunction(object):
657-
object = object.__code__
658-
if istraceback(object):
659-
object = object.tb_frame
660-
if isframe(object):
661-
object = object.f_code
662-
if iscode(object):
663-
return object.co_filename
664-
raise TypeError('module, class, method, function, traceback, frame, or '
665-
'code object was expected, got {}'.format(
666-
type(object).__name__))
669+
670+
try:
671+
return _get_code_object(object).co_filename
672+
except TypeError:
673+
raise TypeError('module, class, method, function, traceback, frame, or '
674+
'code object was expected, got {}'.format(
675+
type(object).__name__)) from None
667676

668677
def getmodulename(path):
669678
"""Return the module name for a given file, or None."""
@@ -811,24 +820,19 @@ def findsource(object):
811820
else:
812821
raise OSError('could not find class definition')
813822

814-
if ismethod(object):
815-
object = object.__func__
816-
if isfunction(object):
817-
object = object.__code__
818-
if istraceback(object):
819-
object = object.tb_frame
820-
if isframe(object):
821-
object = object.f_code
822-
if iscode(object):
823-
if not hasattr(object, 'co_firstlineno'):
824-
raise OSError('could not find function definition')
825-
lnum = object.co_firstlineno - 1
826-
pat = re.compile(r'^(\s*def\s)|(\s*async\s+def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
827-
while lnum > 0:
828-
if pat.match(lines[lnum]): break
829-
lnum = lnum - 1
830-
return lines, lnum
831-
raise OSError('could not find code object')
823+
try:
824+
object = _get_code_object(object)
825+
except TypeError:
826+
raise OSError('could not find code object') from None
827+
828+
if not hasattr(object, 'co_firstlineno'):
829+
raise OSError('could not find function definition')
830+
lnum = object.co_firstlineno - 1
831+
pat = re.compile(r'^(\s*def\s)|(\s*async\s+def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
832+
while lnum > 0:
833+
if pat.match(lines[lnum]): break
834+
lnum = lnum - 1
835+
return lines, lnum
832836

833837
def getcomments(object):
834838
"""Get lines of comments immediately preceding an object's source code.
@@ -951,7 +955,6 @@ def getsourcelines(object):
951955
corresponding to the object and the line number indicates where in the
952956
original source file the first line of code was found. An OSError is
953957
raised if the source code cannot be retrieved."""
954-
object = unwrap(object)
955958
lines, lnum = findsource(object)
956959

957960
if ismodule(object):

Lib/test/test_inspect.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,12 @@ def test_replacing_decorator(self):
539539
def test_getsource_unwrap(self):
540540
self.assertSourceEqual(mod2.real, 130, 132)
541541

542+
def test_getcomments_unwrap(self):
543+
self.assertEqual(inspect.getcomments(mod2.real), '#line 129\n')
544+
545+
def test_getsourcefile_unwrap(self):
546+
self.assertEqual(inspect.getsourcefile(mod2.real), mod2.__file__)
547+
542548
def test_decorator_with_lambda(self):
543549
self.assertSourceEqual(mod2.func114, 113, 115)
544550

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Decorators are now unwrapped in `inspect.getcomments`, `inspect.getfile`,
2+
and `inspect.getsourcefile`, making these functions consistent with
3+
`inspect.getsource`.

0 commit comments

Comments
 (0)