Executing code objects from `fn.__code__` vs code objects from compile

In the below code,

def foo():
    a = 5
    b = 7
    print ('a + b =', a + b)

exec(foo.__code__)

it correctly prints a + b = 12 but the following does not print anything.

import inspect

def foo():
    a = 5
    b = 7
    print ('a + b =', a + b)

source = inspect.getsource(foo)
foo_code = compile(source, '<string>', 'exec')
exec(foo_code)  # does not print anything

I am not able to figure out why that happens as in both cases we are executing the same code object except that the latter is a code object created from compile.

inspect.getsource(foo) doesn’t work for me… I’m not sure why the difference, but would suggest printing source; does it contain the body of the function or does it contain the whole function including def foo():, in which case executing it will only define the function but not call it.

I would suggest you print out source to get an idea of what code you are actually executing. This is debugging 101

I have tried it earlier and it prints:

def foo():
    a = 5
    b = 7
    print ('a + b =', a + b)

Because foo.__code__ is the bytecode for the implementation of the function itself.

Because foo_code is the bytecode for the process of creating the function and binding it to the name foo in whatever namespace. It loads an embedded code object (which is equivalent to the one you got from foo.__code__), and the string 'foo' and uses them to create a function object (using the string 'foo' for the __name__ and the code object for the __code__).

This is not so. Use the dis module to verify.

It won’t work at the REPL, because inspect.getsource uses the function’s .__code__.co_filename to look up the original .py file and copy source code out of it - but in this case there isn’t such a file and the filename is a dummy value. You can reproduce it by just using a multi-line string literal containing the source for the compile call.

It does indeed contain the whole function including the def line.

What I see at the REPL

Here’s the result for the foo_code:

>>> dis.dis(foo_code)
  1           0 LOAD_CONST               0 (<code object foo at 0x7fc94d9325b0, file "<string>", line 1>)
              2 LOAD_CONST               1 ('foo')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (foo)
              8 LOAD_CONST               2 (None)
             10 RETURN_VALUE

Disassembly of <code object foo at 0x7fc94d9325b0, file "<string>", line 1>:
  2           0 LOAD_CONST               1 (5)
              2 STORE_FAST               0 (a)

  3           4 LOAD_CONST               2 (7)
              6 STORE_FAST               1 (b)

  4           8 LOAD_GLOBAL              0 (print)
             10 LOAD_CONST               3 ('a + b =')
             12 LOAD_FAST                0 (a)
             14 LOAD_FAST                1 (b)
             16 BINARY_ADD
             18 CALL_FUNCTION            2
             20 POP_TOP
             22 LOAD_CONST               0 (None)
             24 RETURN_VALUE

As you can see, dis automatically also disassembles the “code object foo”. It matches what we get from foo.__code__:

>>> dis.dis(foo.__code__)
  2           0 LOAD_CONST               1 (5)
              2 STORE_FAST               0 (a)

  3           4 LOAD_CONST               2 (7)
              6 STORE_FAST               1 (b)

  4           8 LOAD_GLOBAL              0 (print)
             10 LOAD_CONST               3 ('a + b =')
             12 LOAD_FAST                0 (a)
             14 LOAD_FAST                1 (b)
             16 BINARY_ADD
             18 CALL_FUNCTION            2
             20 POP_TOP
             22 LOAD_CONST               0 (None)
             24 RETURN_VALUE
3 Likes

Thanks, that’s helpful.