Reputation: 1823
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
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
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