diff --git a/Lib/inspect.py b/Lib/inspect.py index 24c8df72251..bf4f87d5cfa 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -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. diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 042617b60dc..955b2adcb27 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -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) diff --git a/Misc/NEWS b/Misc/NEWS index a1c29dd8ba7..bced023faef 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -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.