Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I display the code of a Python function?

Tags:

python

I defined a function in a Python interactive console (not idle, not ipython) and I needed to see it again many screenfuls later.

Example:

>>> def myf():  
...    print 1
...    print 2
...    print 3
...
>>>
>>> # a lot of scrolling
>>>  

Fake example output below (instead of "name 'print_function' is not defined") to demonstrate what I want:

>>> print_function(myf)
myf()
    print 1
    print 2
    print 3

Is there anything like print_function in Python? If not, how would you implement it?

like image 751
ammt Avatar asked Jan 24 '26 04:01

ammt


2 Answers

You cannot do this in the regular console. iPython keeps a copy of the source in case you want to see it again later on, but the standard Python console does not.

Had you imported the function from a file, you could have used inspect.getsource():

>>> import os.path
>>> import inspect
>>> print inspect.getsource(os.path.join)
def join(a, *p):
    """Join two or more pathname components, inserting '/' as needed.
    If any component is an absolute path, all previous path components
    will be discarded.  An empty last part will result in a path that
    ends with a separator."""
    path = a
    for b in p:
        if b.startswith('/'):
            path = b
        elif path == '' or path.endswith('/'):
            path +=  b
        else:
            path += '/' + b
    return path

but, just to be explicit, inspect.getsource() will fail for functions entered in the interactive console:

>>> def foo(): pass
... 
>>> print inspect.getsource(foo)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/mj/Development/Libraries/buildout.python/parts/opt/lib/python2.7/inspect.py", line 701, in getsource
    lines, lnum = getsourcelines(object)
  File "/Users/mj/Development/Libraries/buildout.python/parts/opt/lib/python2.7/inspect.py", line 690, in getsourcelines
    lines, lnum = findsource(object)
  File "/Users/mj/Development/Libraries/buildout.python/parts/opt/lib/python2.7/inspect.py", line 538, in findsource
    raise IOError('could not get source code')
IOError: could not get source code

because nothing in the interpreter retains the input (other than the readline library, which might save input history, just not in a format directly usable by inspect.getsource()).

like image 67
Martijn Pieters Avatar answered Jan 26 '26 18:01

Martijn Pieters


It's a bit hackish but if this is something that you will be doing a lot, you can use the readline module and a function decorator.

class PrintableFunction(object):
    """A class that contains a function and its start and end points 
    in the readline history"""

    def __init__(self, func, start, end):
        self.start = start
        self.end = end
        self.func = func

    def __call__(self, *args):
        self.func(*args)

    def __str__(self):
        """Just get all the readline history lines from start to end and
        return them"""

        lines = []
        for i in range(self.start, self.end + 1):
            lines.append(readline.get_history_item(i))
        return "\n".join(lines)


class SavedFunction(object):
    """The actual function decorator. It takes one argument, the history 
    line that at which the function definition will begin."""

    def __init__(self, start):
        """Take the starting line as an argument to the decorator. The end
        line is grabbed from the current history after the function has 
        been entered"""

        self.start = start
        self.end = readline.get_current_history_length()

    def __call__(self, func):
        return PrintableFunction(func, self.start, self.end)

You can add these classes to your PYTHONSTARTUP file, so that every time you load an interpreter, you have them available.

>>> @SavedFunction(readline.get_current_history_length() + 1)
... def foo(bar):
...     print(bar)
>>> foo(5)
5
>>> print(foo)
def foo(bar):
    print(bar)

I've created a custom PS1 for myself (in my PYTHONSTARTUP file as well) that shows the current readline history number, meaning I can just quickly add it to the @saved_function argument list, which is easier than fetching it with the readline.get_current_history_length function as above:

[508] @SavedFunction(509)
(509) def foo(bar):
(510)     print(bar)
[511] print(foo)
def foo(bar):
    print(bar)