Issue #24485: Function source inspection fails on closures.

The fix for Issue #21217 introduced a regression that caused
`inspect.getsource` to return incorrect results on nested
functions.  The root cause of the regression was due to
switching the implementation to analyze the underlying
bytecode instead of the source code.

This commit switches things back to analyzing the source code
in a more complete way.  The original bug and the regression
are both fixed by the new source code analysis.
This commit is contained in:
Meador Inge 2015-07-23 22:49:37 -05:00
parent 7039839895
commit 5b718d7f4f
3 changed files with 29 additions and 5 deletions

View File

@ -859,21 +859,37 @@ class BlockFinder:
self.islambda = False
self.started = False
self.passline = False
self.indecorator = False
self.decoratorhasargs = False
self.last = 1
def tokeneater(self, type, token, srowcol, erowcol, line):
if not self.started:
if not self.started and not self.indecorator:
# skip any decorators
if token == "@":
self.indecorator = True
# look for the first "def", "class" or "lambda"
if token in ("def", "class", "lambda"):
elif token in ("def", "class", "lambda"):
if token == "lambda":
self.islambda = True
self.started = True
self.passline = True # skip to the end of the line
elif token == "(":
if self.indecorator:
self.decoratorhasargs = True
elif token == ")":
if self.indecorator:
self.indecorator = False
self.decoratorhasargs = False
elif type == tokenize.NEWLINE:
self.passline = False # stop skipping when a NEWLINE is seen
self.last = srowcol[0]
if self.islambda: # lambdas always end at the first NEWLINE
raise EndOfBlock
# hitting a NEWLINE when in a decorator without args
# ends the decorator
if self.indecorator and not self.decoratorhasargs:
self.indecorator = False
elif self.passline:
pass
elif type == tokenize.INDENT:
@ -913,8 +929,10 @@ def getsourcelines(object):
object = unwrap(object)
lines, lnum = findsource(object)
if ismodule(object): return lines, 0
else: return getblock(lines[lnum:]), lnum + 1
if ismodule(object):
return lines, 0
else:
return getblock(lines[lnum:]), lnum + 1
def getsource(object):
"""Return the text of the source code for an object.

View File

@ -464,7 +464,6 @@ class TestDecorators(GetSourceBase):
def test_getsource_unwrap(self):
self.assertSourceEqual(mod2.real, 130, 132)
@unittest.expectedFailure
def test_decorator_with_lambda(self):
self.assertSourceEqual(mod2.func114, 113, 115)

View File

@ -28,6 +28,9 @@ Core and Builtins
Library
-------
- Issue #22485: Fixed an issue that caused `inspect.getsource` to return incorrect
results on nested functions.
- Issue #22153: Improve unittest docs. Patch from Martin Panter and evilzero.
- Issue #24580: Symbolic group references to open group in re patterns now are
@ -551,6 +554,10 @@ Library
- Issue #23342: Add a subprocess.run() function than returns a CalledProcess
instance for a more consistent API than the existing call* functions.
- Issue #21217: inspect.getsourcelines() now tries to compute the start and end
lines from the code object, fixing an issue when a lambda function is used as
decorator argument. Patch by Thomas Ballinger and Allison Kaptur.
- Issue #24521: Fix possible integer overflows in the pickle module.
- Issue #22931: Allow '[' and ']' in cookie values.