9

The following code throws an exception:

import inspect

def work():
    my_function_code = """def print_hello():
                              print('Hi!')
                       """
    exec(my_function_code, globals())
    inspect.getsource(print_hello)

The code above throws an exception IOError. If I declare the function without using exec (like below), I can get its source code just fine.

import inspect

def work():
    def print_hello():
        print('Hi!')
    inspect.getsource(print_hello)

There's a good reason for me to do something like this.

Is there a workaround for this? Is it possible to do something like this? If not, why?

1
  • @jsbueno answered fine why it's not impossible. There's one more comment from me, that your exec even not patches work function locals() dictionary (that's why it is dumb here), you can change it to exec(my_function_code, globals(), locals()) and you'll see you can use defined closure in future Commented Aug 22, 2012 at 12:01

3 Answers 3

7

I just looked at the inspect.py file after reading @jsbueno's answer, here's what I found :

def findsource(object):
    """Return the entire source file 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 all the lines
    in the file and the line number indexes a line in that list.  An **IOError
    is raised if the source code cannot be retrieved.**"""
    try:
        file = open(getsourcefile(object))  
    except (TypeError, IOError):
        raise IOError, 'could not get source code'
    lines = file.readlines()               #reads the file
    file.close()

It clearly indicates that it tries to open the source file and then reads its content, which is why it is not possible in case of exec.

Sign up to request clarification or add additional context in comments.

Comments

4

That is not even possible. What python does to get to the source of any code it is running is loading the source code file - on disk. It locates this file by looking at the __file__ attribute on the code's module.

The string used to generate a code object trough "exec" or "compiled" is not kept around by the objects resulting from these calls.

You probably could get to look at the code if you set a __file__ variable on the global dictionary of your generated code, and write your source string to that file, prior to calling inspect.getsource.

4 Comments

Bear in mind that I did not even intent do propose a "solution" - I started with "it is not possible".
Code containing exec at all doesn't sound hacky?
Like I said, there's a good reason for using it...if you wanna know, here's a link to an article I wrote: csclub.uwaterloo.ca/~mtahmed/articles/…
So, maybe the better thing to do is writting the source file first, from your dynamically generated code, and them importing it using __import__, instead of using exec. That would be less hacky both ways.
0

This solution didn't exist back then, but since Python 3.4 it does !

You can now monkey patch linecache.getlines in order to make inspect.getsource() to work with code coming from exec(). When you look at the error stack, it stops at findsource() in inspect.py. When you look at the code of findsource(), you'll see a hint:

# Allow filenames in form of "<something>" to pass through.
# `doctest` monkeypatches `linecache` module to enable
# inspection, so let `linecache.getlines` to be called.

And then if you look at this test function you'll see what it means. You can temporarily change one of the core Python function to serve your purpose.

Anyway, here's the solution, starting with Python 3.4:

import linecache
import inspect

def exec_getsource(code):
    getlines = linecache.getlines
    def monkey_patch(filename, module_globals=None):
        if filename == '<string>':
            return code.splitlines(keepends=True)
        else:
            return getlines(filename, module_globals)
    linecache.getlines = monkey_patch
    
    try:
        exec(code)
        #you can now use inspect.getsource() on the result of exec() here
        
    finally:
        linecache.getlines = getlines

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.