Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ def findsource(object):
if not hasattr(object, 'co_firstlineno'):
raise OSError('could not find function definition')
lnum = object.co_firstlineno - 1
pat = re.compile(r'^(\s*def\s)|(\s*async\s+def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
pat = re.compile(r'^(\s*def\s)|(\s*async\s+def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*class\s)|^(\s*@)')
while lnum > 0:
if pat.match(lines[lnum]): break
lnum = lnum - 1
Expand Down
21 changes: 21 additions & 0 deletions Lib/test/inspect_fodder.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,24 @@ async def lobbest(grenade):
raise Exception()
except:
tb = sys.exc_info()[2]

# line 84
def extra_c():
pass
class C:
def func(self):
pass
fr = inspect.currentframe()

# line 92
extra_b = 1
class B:
def func(self):
pass
fr = inspect.currentframe()

# line 99
class A:
def func(self):
pass
fr = inspect.currentframe()
33 changes: 27 additions & 6 deletions Lib/test/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,13 +375,20 @@ def assertSourceEqual(self, obj, top, bottom):
self.assertEqual(inspect.getsource(obj),
self.sourcerange(top, bottom))

def assertNotSourceEqual(self, obj, top, bottom):
self.assertNotEqual(inspect.getsource(obj),
self.sourcerange(top, bottom))

class TestRetrievingSourceCode(GetSourceBase):
fodderModule = mod

def test_getclasses(self):
classes = inspect.getmembers(mod, inspect.isclass)
self.assertEqual(classes,
[('FesteringGob', mod.FesteringGob),
[('A', mod.A),
('B', mod.B),
('C', mod.C),
('FesteringGob', mod.FesteringGob),
('MalodorousPervert', mod.MalodorousPervert),
('ParrotDroppings', mod.ParrotDroppings),
('StupidGit', mod.StupidGit),
Expand All @@ -390,7 +397,10 @@ def test_getclasses(self):
tree = inspect.getclasstree([cls[1] for cls in classes])
self.assertEqual(tree,
[(object, ()),
[(mod.ParrotDroppings, (object,)),
[(mod.A, (object,)),
(mod.B, (object,)),
(mod.C, (object,)),
(mod.ParrotDroppings, (object,)),
[(mod.FesteringGob, (mod.MalodorousPervert,
mod.ParrotDroppings))
],
Expand All @@ -405,7 +415,10 @@ def test_getclasses(self):
tree = inspect.getclasstree([cls[1] for cls in classes], True)
self.assertEqual(tree,
[(object, ()),
[(mod.ParrotDroppings, (object,)),
[(mod.A, (object,)),
(mod.B, (object,)),
(mod.C, (object,)),
(mod.ParrotDroppings, (object,)),
(mod.StupidGit, (object,)),
[(mod.MalodorousPervert, (mod.StupidGit,)),
[(mod.FesteringGob, (mod.MalodorousPervert,
Expand All @@ -418,6 +431,7 @@ def test_getclasses(self):
def test_getfunctions(self):
functions = inspect.getmembers(mod, inspect.isfunction)
self.assertEqual(functions, [('eggs', mod.eggs),
('extra_c', mod.extra_c),
('lobbest', mod.lobbest),
('spam', mod.spam)])

Expand Down Expand Up @@ -552,7 +566,7 @@ def monkey(filename, module_globals=None):
def test_getsource_on_code_object(self):
self.assertSourceEqual(mod.eggs.__code__, 12, 18)

class TestGettingSourceOfToplevelFrames(GetSourceBase):
class TestGettingSourceOfFrames(GetSourceBase):
fodderModule = mod

def test_range_toplevel_frame(self):
Expand All @@ -562,6 +576,14 @@ def test_range_toplevel_frame(self):
def test_range_traceback_toplevel_frame(self):
self.assertSourceEqual(mod.tb, 1, None)

def test_class_frame(self):
self.assertSourceEqual(mod.A.fr, 100, 103)
self.assertNotSourceEqual(mod.A.fr, 85, 86)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation for the assertNotSourceEqual tests? If it's equal to a range, surely you don't have to also test that it's unequal to another range?

self.assertSourceEqual(mod.B.fr, 94, 97)
self.assertNotSourceEqual(mod.B.fr, 85, 86)
self.assertSourceEqual(mod.C.fr, 87, 90)
self.assertNotSourceEqual(mod.C.fr, 85, 86)

class TestDecorators(GetSourceBase):
fodderModule = mod2

Expand Down Expand Up @@ -676,7 +698,6 @@ def test_getsource_on_method(self):
def test_nested_func(self):
self.assertSourceEqual(mod2.cls135.func136, 136, 139)


class TestNoEOL(GetSourceBase):
def setUp(self):
self.tempdir = TESTFN + '_dir'
Expand Down Expand Up @@ -3905,7 +3926,7 @@ def test_main():
TestBoundArguments, TestSignaturePrivateHelpers,
TestSignatureDefinitions, TestIsDataDescriptor,
TestGetClosureVars, TestUnwrap, TestMain, TestReload,
TestGetCoroutineState, TestGettingSourceOfToplevelFrames
TestGetCoroutineState, TestGettingSourceOfFrames
)

if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix inspect.findsource incorrectly returning the line number of the first
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed that inspect.findsource is not documented and the tests also use inspect.getsource though it uses findsource internally. Can this be changed to inspect.getsource? Also please use the below with markdown so that the text is linked to the function docs.

Fix :func:`inspect.getsource` incorrectly returning the line number of the first
function above any given class frame objects.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That might be a bit unfair since more functions than just getsource use it. getcomments and getsourcelines use it directly while getsource uses getsourcelines, using it indirectly. Would it be OK to mention all of them?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last two commits ef1e750 and e4e811d used getsource in the NEWS though findsource was fixed since I think findsource is not documented. I think we can wait for the reviewer on this 👍

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for reference there is an open issue to document functions like findsource : https://bugs.python.org/issue17972

function above any given class frame objects.
Comment on lines +1 to +2
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inspect.findsource remains undocumented now. I'd suggest simply saying that the patch fixes finding class object frames in the inspect module.

Suggested change
Fix inspect.findsource incorrectly returning the line number of the first
function above any given class frame objects.
Fix bug where the :mod:`inspect` module failed to identify the
correct source code for class object frames.