KT.
KT.

Reputation: 11440

Is it possible to access exec-provided globals dictionary from within a function?

Is it possible to access exec-provided globals dictionary from within a function, if the function was defined outside of the exec-ed code (and thus already bound to different __globals__)?

In other words, is there a way to make the following example work?

def f():
    log("Hi")

exec('f()', {'f': f, 'log': print})

In general, is it possible to substitute the __globals__ of a function?

Upvotes: 0

Views: 223

Answers (2)

user2357112
user2357112

Reputation: 281843

This is a pretty weird thing to do, but it's doable.

Your exec call executes the statement f() in the provided globals. It does not execute the body of f in the provided globals. The provided globals are being used in the wrong stack frame. To access those globals from f, you can use stack inspection:

import inspect

def f():
    log = inspect.currentframe().f_back.f_globals['log']
    log('Hi')

exec('f()', {'f': f, 'log': print})

If you want to execute the body of f with the provided globals rather than just gaining access to the globals, you need to make a copy of f with your own custom globals:

import types
my_f = types.FunctionType(f.__code__,
                          {'log': print},
                          f.__name__,
                          f.__defaults__,
                          f.__closure__)
my_f()

The function type constructor is sort of documented; it's not in the online docs, but it is documented in the function type's docstring:

function(code, globals[, name[, argdefs[, closure]]])

Create a function object from a code object and a dictionary.
The optional name string overrides the name from the code object.
The optional argdefs tuple specifies the default argument values.
The optional closure tuple supplies the bindings for free variables.

Upvotes: 2

user10325516
user10325516

Reputation:

Not sure if I am fully correct about the explanation. In short, the example cannot work in Python 3.

The reason is in the combination of 2 circumstances: [1] - exec is a function in Python 3, [2] - the code you try to execute contains function call.

When you provide globals optional argument to the function exec it is local scope of this very function. So the following example works:

exec('log("Hi")', {'log': print})

But the original one does not. Because in the original example you call the function f. It has its own local scope. What Python does? It checks global scope (actual global scope of the program) and innermost scope (local scope of the function f). Both scopes lack for log and you get NameError.

You can get the very same behavior (the same error) with two regular functions:

def f():
    log("Hi")


def f_():
    log = print
    f()

f_()

Upvotes: 0

Related Questions