]> granicus.if.org Git - python/commitdiff
Issue #21217: inspect.getsourcelines() now tries to compute the start and
authorAntoine Pitrou <solipsis@pitrou.net>
Tue, 14 Apr 2015 22:41:29 +0000 (00:41 +0200)
committerAntoine Pitrou <solipsis@pitrou.net>
Tue, 14 Apr 2015 22:41:29 +0000 (00:41 +0200)
end lines from the code object, fixing an issue when a lambda function is
used as decorator argument.  Patch by Thomas Ballinger.

Lib/inspect.py
Lib/test/inspect_fodder2.py
Lib/test/test_inspect.py
Misc/ACKS
Misc/NEWS

index 81b1ce87098bde19cc335b91cecaf5582a0892fe..60890f2eec07789eb6329fb4e55634ca4eca3791 100644 (file)
@@ -32,6 +32,7 @@ __author__ = ('Ka-Ping Yee <ping@lfw.org>',
               'Yury Selivanov <yselivanov@sprymix.com>')
 
 import ast
+import dis
 import enum
 import importlib.machinery
 import itertools
@@ -49,18 +50,10 @@ from operator import attrgetter
 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 @@ def getblock(lines):
         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 @@ 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
+    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.
index e452235cd8e53fe421bf91c95a47270185290843..ab1cd9f50024bbf1a21147b9ef4d8d94b041e2ce 100644 (file)
@@ -110,6 +110,14 @@ def annotated(arg1: list):
 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 @@ def decorator(func):
         return 42
     return fake
 
-#line 121
+#line 129
 @decorator
 def real():
     return 20
index 76f2b47401218e8b43329b3ccf216d34ef826c6b..9e1f546be79da1b2e01d5e9a59555a19dd0641ae 100644 (file)
@@ -392,6 +392,9 @@ class TestRetrievingSourceCode(GetSourceBase):
         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 @@ class TestDecorators(GetSourceBase):
         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 @@ class TestBuggyCases(GetSourceBase):
             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'
index 75a34d4a73b9a675ce01e33c36b64940c86d0cd0..c26ecf4995ae6112b1e3cb13e3e651d66bd58d3d 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -72,6 +72,7 @@ Dwayne Bailey
 Stig Bakken
 Greg Ball
 Luigi Ballabio
+Thomas Ballinger
 Jeff Balogh
 Manuel Balsera
 Matt Bandy
index ff7e543577e8eb35d7995763a9b40c6febb69c05..e3083c1e278edc5e5f2bb1da200d161f85c03b88 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -1,4 +1,4 @@
-+++++++++++
++++++++++++
 Python News
 +++++++++++
 
@@ -9,6 +9,7 @@ Release date: XXX
 
 Core and Builtins
 -----------------
+
 - Issue #22631: Added Linux-specific socket constant CAN_RAW_FD_FRAMES.
   Patch courtesy of Joe Jevnik.
 
@@ -31,6 +32,10 @@ Core and Builtins
 Library
 -------
 
+- 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.
+
 - Issue #23811: Add missing newline to the PyCompileError error message.
   Patch by Alex Shkop.