Python 2 和 Python 3 中 exec 函数的行为
- 2024-12-31 08:37:00
- admin 原创
- 133
问题描述:
Python2
以下代码在和 中给出不同的输出Python3
:
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
印刷:
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
为什么将函数内的Python2
变量绑定到函数字符串中的值,而 while却不这样做?如何实现in的行为?我已经尝试将全局和局部的字典传递给in 函数,但到目前为止没有任何效果。b
`executeexec
Python3Python2
Python3exec
Python3`
- - 编辑 - -
在阅读 Martijns 的回答后,我进一步分析了这一点Python3
。在下面的例子中,我给出了字典locals()
,但打印的内容不仅仅是打印。d
`execd['b']
b`
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
d
和的 id 比较locals()
表明它们是同一个对象。但在这种情况下b
应该与 相同d['b']
。我的示例中有什么问题?
解决方案 1:
exec
Python 2 和Python 3之间存在很大差异。exec()
您将其视为exec
一个函数,但在 Python 2 中它实际上是一个语句。
由于这个差异,你不能在 Python 3 中使用 来更改函数作用域内的局部变量exec
,尽管在 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()
回函数 locals 使用PyFrame_LocalsToFast
,但前提是没有提供globals和locals参数。
正确的解决方法是使用新的命名空间(字典)进行调用exec()
:
def execute(a, st):
namespace = {}
exec("b = {}
print('b:', b)".format(st), namespace)
print(namespace['b'])
文档exec()
对这个限制说得非常明确:
注意:默认局部变量的作用如下函数所述:不应尝试
locals()
修改默认局部变量字典。如果您需要在函数返回后查看代码对局部变量的影响,请传递显式局部变量字典。exec()
解决方案 2:
我认为这是 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 的 bug,因为它有文档记录,但它并不直观。此外,在 中修改局部变量exec
不会改变函数的局部变量这一事实是与 python2 有记录的区别(文档说“如果您需要在函数 exec() 返回后查看代码对局部变量的影响,请传递一个显式局部变量字典”),我更喜欢 python2 的行为。如果在 中设置了一个变量
exec
,而该变量之前不存在,则exec
修改内部字典,除非之后设置了该变量。更新字典的方式似乎存在错误;这个错误允许通过调用之后locals()
来访问在 中设置的值。exec
`locals()`exec
解决方案 3:
总结一下:
Python 2 和 Python 3 中均无 bug
的不同行为
exec
源于exec
在 Python 2 中它是一个语句,而在 Python 3 中它变成了一个函数。
请注意:
我在这里没有讲什么新东西。这只是对所有其他答案和评论中发现的真相的汇总。我在这里所做的只是阐明一些比较模糊的细节。
Python 2 和 Python 3 之间的唯一区别是,exec
在 Python 2 中确实能够改变封闭函数的本地作用域(因为它是一个语句并且可以访问当前的本地作用域),而在 Python 3 中不能再这样做了(因为它现在是一个函数,因此在它自己的本地作用域中运行)。
然而,这种恼怒与这句话无关exec
,它只是源于一个特殊的行为细节:
locals()
返回一些东西,我想称之为“一个范围可变的单例,在调用之后locals()
,它总是只引用本地范围内的所有变量”。
请注意,Python 2 和 3 之间的行为locals()
没有改变。因此,这种行为以及exec
工作方式的改变看起来像是不稳定的,但事实并非如此,因为它只是暴露了一些一直存在的细节。
“引用局部范围内变量的范围可变单例”是什么意思?
它是一个
scope-wise singleton
,因为无论您在同一范围内调用多少次locals()
,返回的对象总是相同的。
+ 因此观察,`id(d) == id(locals())`因为`d`和`locals()`引用同一个对象,同一个单例,因为只能有一个(在不同的范围内你会得到不同的对象,但在同一个范围内你只能看到这个单例)。
是的
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) - 顺便说一下,这是其他单例。
+ 将指向此 Number(1) 的指针存储到`LOCALS['a']`
(其中`LOCALS`应为内部局部范围)
+ 如果尚不存在,则创建`SINGLETON`对象
+ 更新`SINGLETON`,因此它引用了`LOCALS`
+ 存储指针`SINGLETON`到`LOCALS['d']`
+ 顺便说一下,创建 Number(300),它不是*单*例。
+ 将指向这些 Number(300) 的指针存储到`d['a']`
+ 因此`SINGLETON`也更新了。
+ 但`LOCALS`并未更新,所以局部变量还是Number(1 **)**`a``LOCALS['a']`
+ 现在,`locals()`再次被调用,`SINGLETON`被更新。
+ 正如`d`所指的`SINGLETON`,不`LOCALS`,`d`也会改变!
有关这个令人惊讶的细节的更多信息,为什么
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 元组),with 允许使用与 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()
也适用于所有其他函数。
解决方案 4:
恐怕我无法准确解释,但它基本上来自于函数内的 b 是本地的,并且exec()
似乎分配给全局 b。您必须在函数内部和exec 语句内将 b 声明为全局的。
尝试一下:
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)
这让我
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()
函数内部声明它。但我发现这不起作用(这是我无法准确解释的部分)。
扫码咨询,免费领取项目管理大礼包!