使用Python 3中exec'ed字符串中定义的函数

Gre*_*war 20 python python-3.x python-internals

为什么以下python3代码会产生错误?

a='''
def x():
  print(42)
'''

class Test:
    def __init__(self):
        exec(a)
        x()

t = Test()
Run Code Online (Sandbox Code Playgroud)

结果在此消息中:

Traceback (most recent call last):
  File "bug.py", line 11, in <module>
    t = Test()
  File "bug.py", line 9, in __init__
    x()
NameError: global name 'x' is not defined
Run Code Online (Sandbox Code Playgroud)

the*_*eye 16

注意:exec它只是Python 2.x中的一个Simple语句,而它是Python 3.x中的一个函数.

Python 2.7

让我们检查执行所做的更改a.

class Test:
    def __init__(self):
        l, g = locals().copy(), globals().copy()
        exec a           # NOT a function call but a statement
        print locals() == l, globals() == g
        x()

t = Test()
Run Code Online (Sandbox Code Playgroud)

产量

False True
42
Run Code Online (Sandbox Code Playgroud)

这意味着,它改变了locals字典中的内容.如果您locals().keys()在之前和之后打印exec,您将看到x之后exec.根据exex的文档,

在所有情况下,如果省略可选部分,则代码在当前范围内执行.

因此,它完全符合文档所说的内容.

Python 3.x:

当我们在Python 3.x中执行相同的操作时,除了得到错误之外,我们得到类似的结果.

class Test:
    def __init__(self):
        l, g = locals().copy(), globals().copy()
        exec(a)          # Function call, NOT a statement
        print(locals() == l, globals() == g)
        x()
Run Code Online (Sandbox Code Playgroud)

产量

False True
NameError: name 'x' is not defined
Run Code Online (Sandbox Code Playgroud)

甚至功能文档exec都说,

在所有情况下,如果省略可选部分,则代码在当前范围内执行.

但它还包括一个底部的注释,

注意:默认的locals的作用如下所述locals():不应尝试修改默认的locals字典.如果需要在函数exec()返回后查看本地代码的效果,则传递显式的本地字典.

所以,我们好奇地检查locals()文档并找到

注意:不应修改此词典的内容; 更改可能不会影响解释器使用的本地和自由变量的值.

因此,解释器不尊重locals()对象所做的更改.这就是为什么它不承认x在本地范围内的定义.

但是当我们做的时候

def __init__(self):
    exec(a, globals())
    x()
Run Code Online (Sandbox Code Playgroud)

它有效,因为我们将它添加到globals字典中.Python首先尝试x在本地范围内查找,然后在类范围内查找,然后在全局范围内查找,并在那里找到它.所以它执行它没有任何问题.

  • **优秀的评论.**在我的情况下,如果你需要在函数`exec()`返回后看到代码对本地的影响,请传递一个明确的`locals`字典.是_exactly_晦涩的解决方案,我是白白grepping有关.我用grep不再.谢谢! (2认同)

tim*_*geb 9

我假设你使用的是Python3.x,因为在Python2.7中,你的代码对我来说很好.因此,对于Python3.x,请更改该行

exec(a)
Run Code Online (Sandbox Code Playgroud)

exec(a, globals())
Run Code Online (Sandbox Code Playgroud)

为了添加x到全局命名空间.

文档


fam*_*kin 7

Python3 exec也需要globalslocals映射类型,它作为对于给定的代码执行的上下文的可选参数:

exec(object[, globals[, locals]])
Run Code Online (Sandbox Code Playgroud)

默认情况下,本地范围将传入两者.执行的代码可以使用它,也可以修改dict,但它对实际的本地范围没有影响.参见locals()和示例:

a = '''
print(t)

def x():
    print(42)
'''

class Test:
    def __init__(self):
        t = 'locals are accessible'
        # same as calling exec(a, locals())
        exec(a)
        print(locals())
        x()

t = Test()
Run Code Online (Sandbox Code Playgroud)

输出:

locals are accessible
{'x': <function x at 0x6ffffd09af0>,
'self': <__main__.Test object at 0x6ffffce3f90>,
't': 'locals are accessible'}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 13, in __init__
NameError: global name 'x' is not defined
Run Code Online (Sandbox Code Playgroud)

如果您希望在调用x后可用,则exec需要传入全局或自定义范围:

# global scope
class Test:
    def __init__(self):
        exec(a, globals())
        x()

# custom scope
class Test:
    def __init__(self):
        scope = {}
        exec(a, scope)
        scope['x']()
Run Code Online (Sandbox Code Playgroud)