1

I've been tasked with something a bit unusual and unexpectedly puzzling - Display the source code of a particular class's implementation of a method.

In [1]: class Demonstration:
   ...:     def cost():
   ...:         return 42
   ...:     

In [2]: class OtherDemo:
   ...:     def cost():
   ...:         return 43
   ...:  

In this example, I'd want to find the text def cost(): and the following lines at the appropriate indent for either given class.

modules like inspect or dis are nearly what I'm looking for, but I want to display the python code, such as is displayed during a traceback; not bytecode.

I'm also not opposed to parsing the source files, either - is there a library that text editors might use for autocompletion or indent calculations that could find a specific method in a class?

John
  • 173
  • 2
  • 9
  • 1
    Isn't what you're asking for pretty much [`inspect.getsourcelines`](https://docs.python.org/2/library/inspect.html#inspect.getsourcelines)? – Two-Bit Alchemist Aug 26 '15 at 21:20

2 Answers2

3

It sounds like the inspect library is exactly what you need, in particular, the function getsourcelines:

In [1]: def foo(x):
   ...:     x += 3
   ...:     x += 4
   ...:     return x
   ...: 

In [2]: import inspect

In [3]: inspect.getsourcelines(foo)
Out[3]: (['def foo(x):\n', '    x += 3\n', '    x += 4\n', '    return x\n'], 1)

In [4]: source_code = _

In [6]: print(''.join(source_code[0]))
def foo(x):
    x += 3
    x += 4
    return x

From the docs:

Return a list of source lines and starting line number for an object. The argument may be a module, class, method, function, traceback, frame, or code object. The source code is returned as a list of the lines corresponding to the object and the line number indicates where in the original source file the first line of code was found. An IOError is raised if the source code cannot be retrieved.

Two-Bit Alchemist
  • 17,966
  • 6
  • 47
  • 82
2

In Python, because of the ability to dynamically modify anything it can be very tricky to map back to a source definition. The definition, after all, could be created on the fly.

Here's a somewhat simplistic example. Dynamic definitions can be much trickier even than this, and it's especially tricky if implementation occurs in a pre-compiled module.

def make_random_function(coin='Heads'):
    if coin == 'Heads':
        def foo(self, a):
            print a
    elif coin == 'Tails':
        def foo(self, a, b):
            return a + b
    else:
        def foo(self, *args, **kwargs):
            raise ValueError('Invalid coin used to create function.')

    foo.__name__ = "dynamic_foo"
    foo.__doc__ = "Good luck buddy."
    return foo


import random
val = random.random()
if val > 0.51:
    coin = 'Heads'
elif val < 0.49:
    coin = 'Tails'
else:
    coin = 'Trick'

function = make_random_function(coin)
MyType = type("MyType", (object,), {function.__name__:function})

m = MyType()

When I run this and then call m.dynamic_foo() I see this:

In [313]: coin
Out[313]: 'Trick'

In [314]: val
Out[314]: 0.5099718112195031

In [315]: m.dynamic_foo()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-315-70b3caeb205b> in <module>()
----> 1 m.dynamic_foo()

<ipython-input-310-475ea0810d8d> in foo(*args, **kwargs)
      8     else:
      9         def foo(*args, **kwargs):
---> 10             raise ValueError('Invalid coin used to create function.')
     11 
     12     foo.__name__ = "dynamic_foo"

ValueError: Invalid coin used to create function.

In [316]: m
Out[316]: <__main__.MyType at 0x7f37e70b3ad0>

Even if I use inspect.getsourcelines(m.dynamic_foo) it's a bit misleading:

In [319]: inspect.getsourcelines(m.dynamic_foo)
Out[319]: 
([u'        def foo(self, *args, **kwargs):\n',
  u"            raise ValueError('Invalid coin used to create function.')\n"],
 9)

Notice how the function's source shows that its name is "foo" (not "dynamic_foo") and it's not a class method or instance method of MyType or anything. This is technically correct in the sense that it is the actual lines of source code, but it's not necessarily what someone might expect to see, since it's a definition that exists in a manner disconnected from how it gets dynamically injected into a class definition.

And this is a simple example of this kind of dynamic function creation and dynamic class manipulation. The more complicated this gets, the less reliable it is to count on inspecting source lines as a reasonable way to understand the function's implementation.

ely
  • 74,674
  • 34
  • 147
  • 228