https://gist.github.com/thomasballinger/10666031
"""
inspect.getsourcelines incorrectly guesses what lines correspond
to the function foo
see getblock in inspect.py
once it finds a lambda, def or class it finishes it then stops
so get getsourcelines returns only the first two noop decorator
lines of bar, while normal behavior is to return all decorators
as it does for foo
"""
import inspect
from pprint import pprint
def noop(arg):
def inner(func):
return func
return inner
@noop(1)
@noop(2)
def foo():
return 1
@noop(1)
@noop(lambda: None)
@noop(1)
def bar():
return 1
pprint(inspect.getsourcelines(foo))
pprint(inspect.getsourcelines(bar))

This patch adds tests demonstrating broken behavior inspect.getsource and inspect.getsourcelines of decorators containing lambda functions, and modifies inspect.getsourcelines to behave correctly.
We use co_lnotab to extract line numbers on all objects with a code object. inspect.getsourcelines can also take a class, which cannot use co_lnotab as there is no associated code object.
@ballingt and I paired on this patch.
Some open questions about inspect.getsource not created or addressed by this patch:
- Is this a bug that should be patched in previous versions as well?
- the docs for say it can take a traceback. What is the correct behavior here? There aren't any tests at the moment. We suggest the line of code that caused the traceback, i.e. the line at tb.tb_lineno
- We added tests of decorated classes. The source of decorated classes does not include the decorators, which is different than the usual behavior of decorated functions. What is the correct behavior here?
- inspect.getblock and inspect.BlockFinder use the term "block" in a way that is inconsistent with its typical use in the interpreter (that is, in ceval.c). Should this be renamed? If so, to what? ("chunk"?)

"- We added tests of decorated classes. The source of decorated classes does not include the decorators, which is different than the usual behavior of decorated functions. What is the correct behavior here?"
There is an open issue for this, http://bugs.python.org/issue1764286. It has a patch which uses inspect.unwrap in order to unwrap the decorated functions.

It sounds like at least a somewhat functional dis module is a pragmatic requirement for a Python implementation to support introspection, so +1 for reverting to the mandatory dependency on dis rather than duplicating its logic.

I strongly suspect that ac86e5b2d45b is the cause of the regression reported in #24485.
def outer():
def inner():
inner1
from inspect import getsource
print(getsource(outer))
omits the body of inner. Ditto if outer is a method. All is okay if outer is a method and the source of the class is requested.
Could the authors, Allison and Thomas, take a look and try to fix _line_number_helper to pass the added test (and possibly incorporate it into findsource)? Since there is a new issue already, this one can be left closed and further work posted to #24485.

Here's an update on #24485 regression.
Looks like getsource() is now using code objects instead of tokenizer to determine blocks first/last lines.
The problem with this particular case is that "inner" function's code object is completely independent from "outer"'s.
So, for an outer() function below:
def outer():
def inner():
never_reached1
never_reached2
the code object contains the following opcodes:
71 0 LOAD_CONST 1 (<code object inner ...>)
3 LOAD_CONST 2 ('outer1.<locals>.inner')
6 MAKE_FUNCTION 0
9 STORE_FAST 0 (inner)
12 LOAD_CONST 0 (None)
15 RETURN_VALUE
The correct solution is to use co_lnotab along with co_firstlineno to iterate through opcodes recursively accounting for MAKE_FUNCTION's code objects.
*However*, I don't think we can solve this for classes, i.e.
def outer_with_class():
class Foo:
b = 1
a = 2
there is no way (as far as I know) to get information about the Foo class start/end lineno.
I think that the only way we can solve this is to revert the patch for this issue.

> I think that the only way we can solve this is to revert the patch for this issue.
I agree with this. It seems like doing this analysis at the bytecode level is the
wrong approach. Perhaps the syntactical analysis being used before should be beefed
up to handle things like the lambda case.