我不是python的专家,所以在我尝试理解变量范围的细微差别时请耐心等待。
作为描述我面临的问题的简单示例,请说我有以下三个文件。
第一个文件是outside_code.py。由于某些限制,我无法修改此文件。它必须按原样使用。它包含一些在某些时候运行eval的代码(是的,我知道eval是satan的产生,但是这是晚些时候的讨论)。例如,让我们说它包含以下代码行:
def eval_string(x):
return eval(x)
第二个文件是一组用户定义的函数。我们称之为functions.py。它包含一些未知数量的函数定义,例如,让我们说functions.py包含一个函数,定义如下:
def foo(x):
print("Your number is {}!".format(x))
现在我写了第三个文件,让它称之为main.py.其中包含以下代码:
import outside_code
from functions import *
outside_code.eval_string("foo(4)")
我使用*导入functions.py中的所有函数定义,因此main.py可以访问它们,而无需执行函数.foo()。我还导入了outside_code.py,因此我可以访问其核心功能,即包含eval的代码。最后,我在outside_code.py中调用该函数,传递一个与functions.py中定义的函数相关的字符串。
在简化示例中,我希望代码打印出来"您的号码是4!"。但是,我收到一条错误消息,指出' foo'没有定义。这显然意味着outside_code.py中的代码无法访问main.py中存在的相同foo函数。所以我需要让foo可以访问它。谁能告诉我foo目前的范围究竟是什么,以及如何扩展它以覆盖我实际想要使用它的空间?解决问题的最佳方法是什么?
答案 0 :(得分:1)
foo
已导入main.py;它的范围仅限于该文件(当然也包括最初定义的文件)。它在outside_code.py中不存在。
真正的eval
函数接受locals和globals dicts,允许您将元素添加到评估代码的命名空间中。但是,如果您的eval_string
尚未通过这些内容,则无法执行任何操作。
答案 1 :(得分:1)
相关文件:https://docs.python.org/3.5/library/functions.html#eval
eval
使用可选字典将全局名称映射到值
eval('foo(4)', {'foo': foo})
会做你需要的。它将字符串'foo'映射到函数对象foo。
修改
重读您的问题,看起来这对您不起作用。我唯一的另一个想法是尝试
eval_str('eval("foo(4)", {"foo": lambda x: print("Your number is {}!".format(x))})')
但这是一个非常强硬的解决方案,并且无法很好地扩展到不适合lambda的函数。
答案 2 :(得分:1)
您必须将这些名称添加到outside_code
的范围内。如果outside_code
是常规Python模块,您可以直接执行此操作:
import outside_code
import functions
for name in getattr(functions, '__all__', (n for n in vars(functions) if not n[0] == '_')):
setattr(outside_code, name, getattr(functions, name))
这会使所有名称functions
导出(您使用from functions import *
导入),并将对应对象的引用添加到outside_code
,以便eval()
在outside_code.eval_string()
内eval_function()
1}}可以找到它们。
您可以使用ast.parse()
function从表达式生成解析树,然后将其传递给outside_code
,然后从表达式中提取所有全局名称,并仅添加这些名称至eval()
来限制损害,可以这么说,但是你仍然会破坏其他模块命名空间以使其发挥作用。
请注意,这几乎与使用eval()
一样邪恶,但如果你不能告诉其他模块中的eval()
采用命名空间参数,那么它是你唯一的选择。那是因为默认,eval_string()
使用它作为命名空间运行的模块的全局命名空间。
但是,如果您的globals
函数实际上接受了更多参数,请查找命名空间或def eval_string(x, namespace=None):
return eval(x, globals=namespace)
选项。如果存在,该函数可能看起来更像这样:
outside_code.eval_string('foo(4)', vars(functions))
之后你可以这样做:
vars(functions)
其中functions
为您提供fastlane screenshot
模块的命名空间。