我很好奇,是否可以在函数中exec
一个字符串,就好像该字符串直接被exec
替换一样(使用适当的缩进)。我知道在99.9%的情况下,你不应该使用exec
,但我更感兴趣的是这是否可以做到,而不是应该做到
我想要的行为相当于:
GLOBAL_CONSTANT = 1
def test_func():
def A():
return GLOBAL_CONSTANT
def B():
return A()
return B
func = test_func()
assert func() == 1
但我得到的是:
GLOBAL_CONSTANT = 1
EXEC_STR = """
def A():
return GLOBAL_CONSTANT
def B():
return A()
"""
def exec_and_extract(exec_str, var_name):
# Insert code here
func = exec_and_extract(EXEC_STR, 'B')
assert func() == 1
def exec_and_extract(exec_str, var_name):
exec(EXEC_STR) # equivalent to exec(EXEC_STR, globals(), locals())
return locals()[var_name]
调用NameError: name 'A' is not defined
时func()
,因为A
和B
存在于exec_and_extract
的locals()
中,但运行A
或B
时的执行上下文是exec_and_extract
{
def exec_and_extract(exec_str, var_name):
exec(EXEC_STR, locals()) # equivalent to exec(EXEC_STR, locals(), locals())
return locals()[var_name]
NameError: name 'GLOBAL_CONSTANT' is not defined
从func()
内部调用A
时,因为A
的执行上下文是exec_and_extract
的locals()
,它不包含GLOBAL_CONSTANT
def exec_and_extract(exec_str, var_name):
exec(EXEC_STR, globals()) # equivalent to exec(EXEC_STR, globals(), globals())
return globals()[var_name]
工作但污染全局名称空间,而不是等效名称空间
def exec_and_extract(exec_str, var_name):
locals().update(globals())
exec(EXEC_STR, locals()) # equivalent to exec(EXEC_STR, locals(), locals())
return locals()[var_name]
可以工作,但需要将exec_and_extract
的globals()
的全部内容复制到它的locals()
中,如果globals()
很大,这是浪费时间的(当然在这个人为的示例中不适用)。此外,它与“粘贴在代码中”版本有细微的不同,因为如果exec_and_extract
的一个参数恰好是GLOBAL_CONSTANT
(一个糟糕的参数名称),则行为会有所不同(“粘贴在”版本将使用参数值,而此代码将使用全局常量值)
试图掩盖问题陈述中的任何“漏洞”:
exec_str
值应表示可以访问全局或局部范围变量的任意代码李>exec_str
中访问哪些全局范围变量李>exec_and_extract
的后续调用之间不应该有“污染”(在全局名称空间中或其他地方)。i、 在这个例子中,EXEC_STR
的执行不应该让A
在未来对exec_and_extract
的调用中是可引用的李>
那么,制作一份
globals()
dict的副本并从中检索B
怎么样这仍然有效,并且不会污染全局名称空间
@user2357112supportsMonica(响应线程中的注释,因为它包含代码块)
看起来像这样的东西可能有用:
这允许访问全局范围,包括将来的更改,以及访问
exec_str
中定义的其他变量这是不可能的
exec
与局部变量作用域机制的交互不好,而且这种机制太受限制,无法工作。事实上,如果您使用默认的局部变量调用exec
,则执行的字符串中的任何局部变量绑定操作都是未定义的行为,包括普通赋值、函数定义、类定义、导入等。引用docs:此外,由
exec
执行的代码不能return
、break
、yield
,也不能代表调用方执行其他控制流。它可以break
作为已执行代码的一部分的循环,或者return
来自已执行代码中定义的函数,但是它不能与其调用方的控制流交互如果您愿意牺牲与调用函数的局部变量交互的需求(如您在注释中所述),并且您不关心与调用方的控制流交互,那么您可以将代码的AST插入到新函数定义的主体中并执行:
我使用了一种基于AST的方法,而不是字符串格式,以避免在输入中意外地将额外缩进插入三引号字符串等问题
inspect
允许我们使用调用exec_and_extract
的人的全局变量,而不是exec_and_extract
自己的全局变量,即使调用方位于不同的模块中在执行的代码中定义的函数看到的是实际的全局变量,而不是副本
修改后的AST中的额外包装器函数避免了其他情况下可能出现的一些范围问题;特别是,
B
将无法在示例代码中看到A
的定义相关问题 更多 >
编程相关推荐