46

以下代码给出了 inPython2和 in不同的输出Python3

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

为什么将函数内部Python2的变量绑定到函数字符串中的值,而没有这样做?我怎样才能实现in的行为?我已经尝试将字典传递给全局变量和局部变量以在其中起作用,但到目前为止没有任何效果。bexecuteexecPython3Python2Python3execPython3

- - 编辑 - -

在阅读了 Martijns 的回答后,我进一步分析了这一点Python3。在下面的示例中,我给出了locals()字典dexecd['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

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

4

4 回答 4

52

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 = {}\nprint('b:', b)".format(st), namespace)
    print(namespace['b'])

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

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

于 2013-02-26T10:59:11.490 回答
8

我会说这是python3的一个错误。

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() 之后查看代码对局部变量的影响,请传递一个显式的局部变量字典Returns"),我更喜欢 python2 的行为。
  • 如果在其中设置了一个变量exec并且该变量之前不存在,则exec修改内部字典,除非该变量是在之后设置的。似乎locals()更新字典的方式有错误;exec此错误通过调用locals()after可以访问其中设置的值exec
于 2015-05-19T21:50:56.403 回答
4

把它们加起来:

  • Python 2 和 Python 3 中都没有错误
  • 的不同行为exec源于execPython 2 中的语句,而它成为 Python 3 中的函数。

请注意:

我在这里不说什么新鲜事。这只是在所有其他答案和评论中发现的真相的集合。我在这里尝试的只是为一些更晦涩的细节带来亮点。

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

然而,愤怒与声明无关exec,它仅源于一个特殊的行为细节:

locals()返回一些东西,我想称之为“一个范围可变的单例,在调用之后locals(),总是只引用本地范围内的所有变量”。

请注意,locals()Python 2 和 3 之间的行为没有改变。因此,这种行为以及exec工作方式的改变看起来是不稳定的,但并非如此,因为它只是暴露了一些细节,这些细节一直都存在。

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

  • 它是 a scope-wise singleton,因为无论您在同一范围内多久调用locals()一次,返回的对象总是相同的。
    • 因此观察到, that 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) - 这是其他一些单例,顺便说一句。
    • 将指向此 Number(1) 的指针存储到LOCALS['a']
      (其中LOCALS应为内部本地范围)
    • 如果不存在,则创建SINGLETON对象
    • update SINGLETON,所以它引用了所有条目LOCALS
    • SINGLETON存储 in的指针LOCALS['d']
    • 创建 Number(300),它不是单例,顺便说一句。
    • 将指向这些 Number(300) 的指针存储到d['a']
    • 因此SINGLETON也更新了。
    • LOCALS没有更新,所以局部变量aorLOCALS['a']仍然是 Number(1)
    • 现在,locals()再次调用,SINGLETON更新。
    • asdSINGLETON,不LOCALSd也改变!

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

但请不要忘记:数字是不可变的,因此如果您尝试将数字更改为另一个值,您实际上会创建另一个对象。

结论:

您无法将execPython 2 的行为恢复到 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 中,您无法优化 across exec,因为包含不可变内容的所有局部变量的状态可能会发生不可预测的变化。这不能再发生了。现在,函数调用的常规规则也适用于exec()所有其他函数。

于 2017-11-23T18:07:26.027 回答
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()函数内声明它。但我发现这不起作用(这是我无法准确解释的部分)。

于 2013-02-26T10:56:33.163 回答