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

问题描述 投票:33回答:4

以下代码在Python2Python3中给出了不同的输出:

from sys import version

print(version)

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

Python2印刷品:

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

Python3印刷品:

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

为什么Python2b函数中的变量execute绑定到exec函数的字符串中的值,而Python3不这样做?我怎样才能在Python2中实现Python3的行为?我已经尝试将全局和本地的字典传递给exec中的Python3函数,但到目前为止还没有任何工作。

---编辑---

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

from sys import version

print(version)

def execute(a, st):
    b = 42
    d = locals()
    exec("b = {}\nprint('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 python-2.7 python-3.x exec
4个回答
39
投票

Python 2中的exec和Python 3中的exec()之间存在很大差异。您将exec视为一个函数,但它确实是Python 2中的一个声明。

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

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

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

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

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

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

def execute(a, st):
    namespace = {}
    exec("b = {}\nprint('b:', b)".format(st), namespace)
    print(namespace['b'])

exec() documentation对此限制非常明确:

注意:默认的locals的行为如下面的函数locals()所述:不应尝试修改默认的locals字典。如果您需要在函数exec()返回后查看代码对locals的影响,则传递显式的locals字典。


5
投票

我会说这是python3的一个bug。

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

打印“2”。

def u():
    exec("a=2")
    a=2
    print(a)
u()

打印“2”。

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

失败了

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

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

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()

输出

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

并且

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()

输出

{'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()更新字典的方式有一个错误;这个bug可以通过在exec之后调用locals()来访问exec中的值集。

2
投票

把它们加起来:

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

请注意:

我在这里不说任何新的东西。这只是在所有其他答案和评论中找到的真相的集合。我在这里尝试的只是为一些更加模糊的细节带来光明。

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

有关这个令人惊讶的细节的更多信息,为什么1是单身,而300不是,请参阅https://stackoverflow.com/a/306353

但请不要忘记:数字是不可变的,因此如果您尝试将数字更改为其他值,则可以有效地创建另一个对象。

结论:

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

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

exec "code"

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

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

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

最后一句话:

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

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


1
投票

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

试试这个:

from sys import version

print(version)

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

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

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

哪能给我

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()函数中声明它。但我发现这不起作用(这是我无法准确解释的部分)。

© www.soinside.com 2019 - 2024. All rights reserved.