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.
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 81b1ce8..60890f2 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -32,6 +32,7 @@
'Yury Selivanov <yselivanov@sprymix.com>')
import ast
+import dis
import enum
import importlib.machinery
import itertools
@@ -49,18 +50,10 @@
from collections import namedtuple, OrderedDict
# Create constants for the compiler flags in Include/code.h
-# We try to get them from dis to avoid duplication, but fall
-# back to hard-coding so the dependency is optional
-try:
- from dis import COMPILER_FLAG_NAMES as _flag_names
-except ImportError:
- CO_OPTIMIZED, CO_NEWLOCALS = 0x1, 0x2
- CO_VARARGS, CO_VARKEYWORDS = 0x4, 0x8
- CO_NESTED, CO_GENERATOR, CO_NOFREE = 0x10, 0x20, 0x40
-else:
- mod_dict = globals()
- for k, v in _flag_names.items():
- mod_dict["CO_" + v] = k
+# We try to get them from dis to avoid duplication
+mod_dict = globals()
+for k, v in dis.COMPILER_FLAG_NAMES.items():
+ mod_dict["CO_" + v] = k
# See Include/object.h
TPFLAGS_IS_ABSTRACT = 1 << 20
@@ -888,6 +881,14 @@
pass
return lines[:blockfinder.last]
+def _line_number_helper(code_obj, lines, lnum):
+ """Return a list of source lines and starting line number for a code object.
+
+ The arguments must be a code object with lines and lnum from findsource.
+ """
+ _, end_line = list(dis.findlinestarts(code_obj))[-1]
+ return lines[lnum:end_line], lnum + 1
+
def getsourcelines(object):
"""Return a list of source lines and starting line number for an object.
@@ -899,8 +900,16 @@
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
+ elif iscode(object):
+ return _line_number_helper(object, lines, lnum)
+ elif isfunction(object):
+ return _line_number_helper(object.__code__, lines, lnum)
+ elif ismethod(object):
+ return _line_number_helper(object.__func__.__code__, lines, lnum)
+ 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/inspect_fodder2.py b/Lib/test/inspect_fodder2.py
index e452235..ab1cd9f 100644
--- a/Lib/test/inspect_fodder2.py
+++ b/Lib/test/inspect_fodder2.py
@@ -110,6 +110,14 @@
def keyword_only_arg(*, arg):
pass
+@wrap(lambda: None)
+def func114():
+ return 115
+
+class ClassWithMethod:
+ def method(self):
+ pass
+
from functools import wraps
def decorator(func):
@@ -118,7 +126,7 @@
return 42
return fake
-#line 121
+#line 129
@decorator
def real():
return 20
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index 76f2b47..9e1f546 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -392,6 +392,9 @@
finally:
linecache.getlines = getlines
+ def test_getsource_on_code_object(self):
+ self.assertSourceEqual(mod.eggs.__code__, 12, 18)
+
class TestDecorators(GetSourceBase):
fodderModule = mod2
@@ -402,7 +405,10 @@
self.assertSourceEqual(mod2.gone, 9, 10)
def test_getsource_unwrap(self):
- self.assertSourceEqual(mod2.real, 122, 124)
+ self.assertSourceEqual(mod2.real, 130, 132)
+
+ def test_decorator_with_lambda(self):
+ self.assertSourceEqual(mod2.func114, 113, 115)
class TestOneliners(GetSourceBase):
fodderModule = mod2
@@ -497,6 +503,9 @@
self.assertRaises(IOError, inspect.findsource, co)
self.assertRaises(IOError, inspect.getsource, co)
+ def test_getsource_on_method(self):
+ self.assertSourceEqual(mod2.ClassWithMethod.method, 118, 119)
+
class TestNoEOL(GetSourceBase):
def __init__(self, *args, **kwargs):
self.tempdir = TESTFN + '_dir'