Python 2和Python 3中exec函数的行为

Behavior of exec function in Python 2 and Python 3

以下代码在Python2Python3中提供不同的输出:

1
2
3
4
5
6
7
8
9
10
11
from sys import version

print(version)

def execute(a, st):
    b = 42
    exec("b = {}
print('b:', b)"
.format(st))
    print(b)
a = 1.
execute(a,"1.E6*a")

Python2打印:

1
2
3
2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)]
('b:', 1000000.0)
1000000.0

Python3打印:

1
2
3
3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
b: 1000000.0
42

为什么Python2execute函数内的变量b绑定到exec函数的字符串中的值,而Python3不执行此操作? 如何在Python3中实现Python2的行为? 我已经尝试将全局变量和本地变量的字典传递给Python3中的exec函数,但到目前为止还没有任何工作。

---编辑---

在阅读Martijns回答后,我用Python3进一步分析了这一点。 在下面的示例中,我将locals()字典设为dexec,但d['b']打印的内容不仅仅是打印b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from sys import version

print(version)

def execute(a, st):
    b = 42
    d = locals()
    exec("b = {}
print('b:', b)"
.format(st), globals(), d)
    print(b)                     # This prints 42
    print(d['b'])                # This prints 1000000.0
    print(id(d) == id(locals())) # This prints True
a = 1.
execute(a,"1.E6*a")

3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
b: 1000000.0
42
1000000.0
True

dlocals()的id的比较表明它们是同一个对象。 但在这些条件下,b应与d['b']相同。 我的例子有什么问题?


Python 2中的exec与Python 3中的exec()之间存在很大差异。您将exec视为一个函数,但它实际上是Python 2中的一个语句。

由于存在这种差异,您无法使用exec在Python 3中更改函数范围中的局部变量,即使它在Python 2中是可能的。甚至不是先前声明的变量。

locals()仅反映一个方向的局部变量。以下从未在2或3中起作用:

1
2
3
4
def foo():
    a = 'spam'
    locals()['a'] = 'ham'
    print(a)              # prints 'spam'

在Python 2中,使用exec语句意味着编译器知道关闭本地作用域优化(例如,从LOAD_FAST切换到LOAD_NAME,以在本地和全局作用域中查找变量)。当exec()是一个函数时,该选项不再可用,并且现在始终优化函数范围。

此外,在Python 2中,exec语句使用PyFrame_LocalsToFastlocals()中找到的所有变量显式复制回函数本地,但前提是没有提供全局和局部参数。

正确的解决方法是为exec()调用使用新的命名空间(字典):

1
2
3
4
5
def execute(a, st):
    namespace = {}
    exec("b = {}
print('b:', b)"
.format(st), namespace)
    print(namespace['b'])

exec()文档非常明确地说明了这个限制:

Note: The default locals act as described for function locals() below: modifications to the default locals dictionary should not be attempted. Pass an explicit locals dictionary if you need to see effects of the code on locals after function exec() returns.


我会说这是python3的一个bug。

1
2
3
4
def u():
    exec("a=2")
    print(locals()['a'])
u()

打印"2"。

1
2
3
4
5
def u():
    exec("a=2")
    a=2
    print(a)
u()

打印"2"。

1
2
3
4
5
def u():
    exec("a=2")
    print(locals()['a'])
    a=2
u()

失败了

1
2
3
4
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
  File"<stdin>", line 3, in u
KeyError: 'a'

---编辑---
另一个有趣的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
def u():
    a=1
    l=locals()
    exec("a=2")
    print(l)
u()
def u():
    a=1
    l=locals()
    exec("a=2")
    locals()
    print(l)
u()

输出

1
2
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 1}

并且

1
2
3
4
5
6
7
8
9
10
11
12
13
def u():
    l=locals()
    exec("a=2")
    print(l)
    print(locals())
u()
def u():
    l=locals()
    exec("a=2")
    print(l)
    print(locals())
    a=1
u()

输出

1
2
3
4
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 2}
{'l': {...}}

显然,exec对本地人的行动如下:

  • 如果变量在exec中设置且此变量是局部变量,则exec修改内部字典(由locals()返回的字典)并且不会将其返回到其原始状态。对locals()的调用更新了字典(如python文档的第2部分所述),并且忘记了exec中设置的值。
    调用locals()来更新字典的需要不是python3的错误,因为它已被记录,但它不直观。此外,exec中的局部变量不会改变函数的局部变量这一事实与python2存在文件差异(文档说"如果你需要在函数后看到代码对本地代码的影响,请传递一个显式的本地字典exec()返回"),我更喜欢python2的行为。
  • 如果变量在exec中设置且此变量之前不存在,则exec将修改内部字典,除非之后设置变量。似乎locals()更新字典的方式存在错误;此错误通过在exec之后调用locals()来访问exec中的值集。


把它们加起来:

  • Python 2和Python 3中都没有错误
  • exec的不同行为源于exec是Python 2中的一个语句,而它在Python 3中成为一个函数。
  • Please note:

    Ok.

    I do not tell anything new here. This is just an assembly of the truth
    out there found in all the other answers and comments.
    All I try here is to bring light to some of the more obscure details.

    Ok.

    Python 2和Python 3之间的唯一区别是,实际上,exec能够在Python 2中更改封闭函数的本地范围(因为它是一个语句并且可以访问当前的本地范围)并且不能这样做已经在Python 3中了(因为它现在是一个函数,所以在它自己的本地范围内运行)。

    然而,这种烦恼与exec语句无关,它只是源于一个特殊的行为细节:

    locals()返回一些东西,我想称之为"范围明确的可变单例,在调用locals()之后,始终只引用本地范围内的所有变量"。

    请注意,locals()的行为在Python 2和3之间没有变化。因此,这种行为以及exec如何工作的变化看起来像是不稳定,但不是,因为它只是暴露了一些细节,它总是在那里。

    "在本地范围内引用变量的范围明确的可变单例"是什么意思?

  • 它是scope-wise singleton,因为无论您在同一范围内调用locals()的频率如何,返回的对象始终是相同的。

  • 因此,观察id(d) == id(locals()),因为dlocals()指的是同一个对象,相同的单身,因为只能有一个(在不同的范围内你得到一个不同的对象,但是在相同的范围内你只看到这一个)。
  • 它是mutable,因为它是普通对象,所以你可以改变它。

  • locals()强制对象中的所有条目再次引用本地范围中的变量。
  • 如果你在对象中改变某些东西(通过d),这会改变对象,因为它是一个普通的可变对象。
  • 单例的这些更改不会传播回本地范围,因为对象中的所有条目都是references to the variables in the local scope。因此,如果您更改条目,则会更改单例对象,而不会更改"更改引用之前引用指向的位置"的内容(因此您不会更改局部变量)。

  • 在Python中,字符串和数字不可变。这意味着,如果您为某个条目指定了某些内容,则不会更改该条目所指向的对象,而是引入一个新对象并将该引用分配给该条目。例:

    1
    2
    3
    4
    5
    6
    a = 1
    d = locals()
    d['a'] = 300
    # d['a']==300
    locals()
    # d['a']==1
  • 除了优化,这样做:

  • 创建新对象Number(1) - 这是其他一些单例,BTW。
  • 将指向此Number(1)的指针存储到LOCALS['a']
    (其中LOCALS应为内部本地范围)
  • 如果尚不存在,请创建SINGLETON对象
  • 更新SINGLETON,因此它引用LOCALS中的所有条目
  • SINGLETON的指针存储到LOCALS['d']
  • 创建数字(300),这不是单身,BTW。
  • 将指向这些Number(300)的指针存储到d['a']
  • 因此SINGLETON也会更新。
  • LOCALS未更新,
    所以局部变量aLOCALS['a']仍然是数字(1)
  • 现在,再次调用locals(),更新SINGLETON
  • 由于d指的是SINGLETON,而不是LOCALSd也会发生变化!
  • For more on this surprising detail, why 1 is a singleton while 300 is not, see https://stackoverflow.com/a/306353

    Ok.

    But please do not forget: Numbers are immutable, so if you try to change a number to another value, you effectively create another object.

    Ok.

    结论:

    你无法将Python 2的exec行为恢复到Python 3(除非通过更改代码),因为无法再改变程序流之外的局部变量。

    但是,您可以将Python 3的行为引入Python 2,这样您今天就可以编写运行相同的程序,无论它们是使用Python 3还是Python 2运行。这是因为在(较新的)Python 2中也可以使用带有参数的函数的exec(事实上,那些是2或3元组),允许使用与Python 3中已知的相同语义相同的语法:

    1
    exec"code"

    (仅适用于Python 2)变为(适用于Python 2和3):

    1
    exec("code", globals(), locals())

    但请注意,"code"不能再以这种方式改变本地封闭范围。另请参见https://docs.python.org/2/reference/simple_stmts.html#exec

    最后一句话:

    Python 3中exec的更改很好。因为优化。

    在Python 2中,您无法跨exec进行优化,因为包含不可变内容的所有局部变量的状态可能会发生不可预测的变化。这不可能再发生了。现在,函数调用的通常规则也适用于exec(),就像所有其他函数一样。

    好。


    我恐怕无法完全解释它,但它主要来自于函数内部的b是局部的,并且exec()似乎分配给全局b。你必须在函数内部和exec语句中声明b是全局的。

    试试这个:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    from sys import version

    print(version)

    def execute1(a, st):
        b = 42
        exec("b = {}
    print('b:', b)"
    .format(st))
        print(b)

    def execute2(a, st):
        global b
        b = 42
        exec("global b; b = {}
    print('b:', b)"
    .format(st))
        print(b)

    a = 1.
    execute1(a,"1.E6*a")
    print()
    execute2(a,"1.E6*a")
    print()
    b = 42
    exec("b = {}
    print('b:', b)"
    .format('1.E6*a'))
    print(b)

    哪能给我

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    3.3.0 (default, Oct  5 2012, 11:34:49)
    [GCC 4.4.5]
    b: 1000000.0
    42

    b: 1000000.0
    1000000.0

    b: 1000000.0
    1000000.0

    您可以看到在函数外部,自动拾取全局b。在函数内部,您将打印本地b。

    请注意,我认为exec()总是首先使用全局b,因此在execute2()中,您不需要在exec()函数内声明它。但我发现这不起作用(这是我无法准确解释的部分)。