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

2024-12-31 08:37:00
admin
原创
134
摘要:问题描述:Python2以下代码在和 中给出不同的输出Python3:from sys import version print(version) def execute(a, st): b = 42 exec("b = {} print('b:', b)".forma...

问题描述:

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`executeexecPython3Python2Python3execPython3`

- - 编辑 - -

在阅读 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:

execPython 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_FASTLOAD_NAME,在局部和全局作用域中查找变量)。作为exec()函数,该选项不再可用,并且函数作用域现在始终是经过优化的。

此外,在 Python 2 中,该exec语句明确地将在中找到的所有变量复制locals()回函数 locals 使用PyFrame_LocalsToFast,但前提是没有提供globalslocals参数。

正确的解决方法是使用新的命名空间(字典)进行调用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()函数内部声明它。但我发现这不起作用(这是我无法准确解释的部分)。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   3998  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2749  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   85  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   96  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   83  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用