capitalistcuttle
capitalistcuttle

Reputation: 1823

How do I execute a function inside a namespace dict?

Say I have the following dict:

In [6]: scope
Out[6]: {'bar': <function bar>, 'foo': <function foo>}

And foo and bar are:

def foo():
    return 5

def bar(x):
    return foo() + x

I want to run bar(1), but it will need to find foo(). Is there any way to run bar() in the scope namespace so it finds foo()?

I don't know precisely which function in scope bar will need, so I need a general method of running bar in the scope namespace. I don't have the source code for and cannot modify either function to accept a dict.

It seems like functions have a __closure__ attribute, but it's immutable. There is also a __globals__ attribute, but that just points to globals(). I've seen some answers on SO that update locals() but I'd like to leave locals() untouched.

I tried an eval in scope, but get a NameError for foo:

In [12]: eval(scope['bar'](1), scope)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-12-f6305634f1da> in <module>()
----> 1 eval(scope['bar'](1), scope)

<string> in bar(x)

NameError: global name 'foo' is not defined

Is there some simple way I'm missing?

Upvotes: 5

Views: 1091

Answers (2)

martineau
martineau

Reputation: 123473

Using eval() will work, however there are two problems with the way you tried to use it. For starters, its first argument, expression, should be a string. Secondly, since you're referencing scope in this expression and passing it to eval() as the globals namespace argument, you'll need to add it to itself.

This illustrates what I mean:

def foo():
    return 5

def bar(x):
    return foo() + x

scope = {'scope': {'bar': bar, 'foo':foo}}  # define self-referential scope
expression = "scope['bar'](42)"  # explicit reference to `scope`
print('eval({!r}, scope) returns {}'.format(expression, eval(expression, scope)))

However, in this specific case, it would be simpler to just let eval() directly look-up the value of bar in the scope dictionary namespace itself (instead of via scope['scope']['bar']):

scope = {'bar': bar, 'foo':foo}
expression = 'bar(42)'
print('eval({!r}, scope) returns {}'.format(expression, eval(expression, scope)))

Upvotes: 2

Matthew Trevor
Matthew Trevor

Reputation: 14961

One approach is to create new functions with a shared globals dictionary:

from types import FunctionType

def scopify(**kwargs):
    scoped = dict()
    for name, value in kwargs.items():
        if isinstance(value, FunctionType):
            scoped[name] = FunctionType(value.__code__, scoped, name)
        else:
            scoped[name] = value
    return scoped

scope = scopify(
    foo = lambda: baz,
    bar = lambda x: foo() + x,
    baz = 5,
)

>>> scope['foo']
5
>>> scope['bar'](10)
15
>>> scope['baz'] = 100
>>> scope['bar'](10)
110

Upvotes: 4

Related Questions