Python 模块和变量作用域

Python 模块和变量作用域,python,python-3.x,Python,Python 3.x,我不是python方面的专家,所以在我试图理解变量作用域的细微差别时请耐心听我说 作为一个描述我所面临问题的简单示例,假设我有以下三个文件 第一个文件位于_code.py之外。由于某些限制,我无法修改此文件。必须照原样去做。它包含一些在某个时刻运行eval的代码(是的,我知道eval是撒旦的产物,但这是以后的讨论)。例如,假设它包含以下代码行: def eval_string(x): return eval(x) 第二个文件是一组用户定义的函数。我们称之为functions.py。它包

我不是python方面的专家,所以在我试图理解变量作用域的细微差别时请耐心听我说

作为一个描述我所面临问题的简单示例,假设我有以下三个文件

第一个文件位于_code.py之外。由于某些限制,我无法修改此文件。必须照原样去做。它包含一些在某个时刻运行eval的代码(是的,我知道eval是撒旦的产物,但这是以后的讨论)。例如,假设它包含以下代码行:

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应该可以访问它们,而无需执行类似functions.foo()的操作。我还导入了outside_code.py,以便访问它的核心功能,即包含eval的代码。最后,我在outside_code.py中调用函数,传递一个与functions.py中定义的函数相关的字符串


在简化的示例中,我希望代码打印出“您的号码是4!”。但是,我得到一个错误,指出没有定义“foo”。这显然意味着,outside_code.py中的代码无法访问main.py中存在的同一个foo函数。所以我需要让foo可以访问它。有谁能告诉我foo目前的范围是什么,以及我如何扩展它以覆盖我真正想要使用它的空间?解决我的问题的最佳方法是什么?

foo
已导入main.py;其范围仅限于该文件(当然也限于最初定义它的文件)。它不存在于outside_code.py中


real
eval
函数接受局部和全局dict,允许您将元素添加到evaluted代码的命名空间中。但是,如果您的
eval\u字符串
尚未传递这些值,则无法执行任何操作。

相关文档:

eval
采用可选字典将全局名称映射到值

eval('foo(4)', {'foo': foo})
我会做你需要的。它将字符串“foo”映射到函数对象foo

编辑

重读你的问题,这似乎对你不起作用。我唯一的另一个想法就是尝试

eval_str('eval("foo(4)", {"foo": lambda x: print("Your number is {}!".format(x))})')

但这是一个非常粗糙的解决方案,不能很好地扩展到不适合lambdas的函数。

您必须将这些名称添加到
的范围之外。如果
outside\u 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))
这将获取所有名称
函数
导出(您将使用
从函数导入*
导入),并将对相应对象的引用添加到
外部代码
,以便
eval()
内部
外部代码。eval\u string()
可以找到它们

在将表达式传递给
eval_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
模块的名称空间。

谢谢。我试图理解变量(函数)。当我查看它里面的内容时,我得到了一个包含functions.py中定义的变量的字典,但它也包含了很多其他随机的东西,比如uuu doc_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu,等等。我如何在没有所有额外内容的情况下,获得我在代码中实际定义的内容?@K.Mao:这是该模块的完整名称空间。所有这些名称都可用于在该模块中执行的表达式。您可以创建该词典的新副本,仅限于名称不以
\uu
开头或在
\uuu all\uu
中列出的名称(如果已定义)。将另一个文件的完整名称空间作为参数传递到eval中有什么不好的地方吗?我会意外地覆盖内置的重要内容吗?还是可以?@K.Mao:
eval
是邪恶的,我们已经提到了吗?它当然可以在名称空间中做任何它喜欢的事情。如果您担心,您可以使用
dict(vars(functions))
创建一个副本,但是如果表达式导入
sys
它可以进入
sys.modules['functions']
,仍然会造成严重破坏。这就是为什么我们不建议使用
eval()