Py学习  »  Python

exec函数未按预期工作(python)[重复]

James Thorn • 4 年前 • 825 次点击  

下面的代码给出了不同的输出 Python2 并在 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")

蟒蛇2 印刷品:

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

蟒蛇3 印刷品:

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

为什么? 蟒蛇2 绑定变量 b execute 函数的字符串中的值 exec 函数,而 蟒蛇3 不是这样吗?我怎样才能达到 蟒蛇2 在里面 蟒蛇3 ?我已经试着把全球和本地的字典传给 执行程序 中的函数 蟒蛇3 但到目前为止还没什么效果。

---编辑---

在阅读了Martijns的答案之后,我进一步分析了这一点 蟒蛇3 . 在下面的示例中,我给出了 locals() 措辞 d 执行程序 但是 d['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

的ID比较 D 本地人() 显示它们是同一对象。但在这种情况下 应该和 D′b′ . 我的例子有什么问题?

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/38604
 
825 次点击  
文章 [ 4 ]  |  最新文章 4 年前
user707650
Reply   •   1 楼
user707650    11 年前

恐怕我不能准确地解释它,但它基本上是由于函数内部的b是局部的,并且 exec() 似乎要分配给全局b。您必须在函数内声明b为全局, 在exec语句中。

试试这个:

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。

注意,我本以为 执行() 始终首先使用全局B,以便 execute2() ,您不需要在 执行() 功能。但我发现这行不通(这是我无法准确解释的部分)。

Tino
Reply   •   2 楼
Tino    6 年前

总而言之:

  • 在python 2和python 3中没有bug
  • 不同的行为 exec 源于 执行程序 在python 2中是一个语句,而在python 3中它变成了一个函数。

请注意:

我这里什么都不说。这只是事实的集合 在所有其他答案和评论中都能找到。 我在这里所要做的就是揭示一些更加模糊的细节。

python 2和python 3的唯一区别是, 执行程序 能够在python 2中更改封闭函数的本地作用域(因为它是一个语句并且可以访问当前的本地作用域),并且在python 3中不能再这样做(因为它现在是一个函数,所以在它自己的本地作用域中运行)。

然而,这种刺激与 执行程序 声明,它只源于一个特殊的行为细节:

locals() 返回某个值,我要调用它“在调用 本地人() ,始终只引用本地作用域中的所有变量。

请注意 本地人() 未在python 2和3之间更改。所以,这种行为加上如何改变 执行程序 作品看起来很不稳定,但事实并非如此,因为它只是暴露了一些细节,而这些细节总是存在的。

“引用局部作用域中变量的作用域可变单例”是什么意思?

  • 它是一个 scope-wise singleton 不管你多久打一次电话 本地人() 在同一范围内,返回的对象始终相同。
    • 因此观察到 id(d) == id(locals()) ,因为 d 本地人() 引用同一个对象,同一个单例,因为只能有一个(在不同的范围中,您会得到不同的对象,但在相同的范围中,您只能看到这个单例)。
  • 它是 mutable ,因为它是一个普通对象,所以您可以更改它。
    • 本地人() 强制对象中的所有条目再次引用本地范围中的变量。
    • 如果您更改了对象中的某些内容(通过 D ,这会改变对象,因为它是一个普通的可变对象。
  • 由于对象中的所有条目都是 references to the variables in the local scope . 因此,如果您更改条目,这些条目将更改singleton对象,而不是“在更改引用之前指向的引用”的内容(因此您不会更改局部变量)。

    • 在python中,字符串和数字是不可变的。这意味着,如果您为一个条目分配了一些东西,您不会更改条目指向的对象,而是引入一个新的对象,并为该条目分配一个对该对象的引用。例子:

      a = 1
      d = locals()
      d['a'] = 300
      # d['a']==300
      locals()
      # d['a']==1
      

    除了优化,这还可以:

    • 创建新的对象编号(1)-这是一些其他的单例,btw。
    • 将指向此数字(1)的指针存储到 LOCALS['a']
      (何处) LOCALS 应为内部本地范围)
    • 如果不存在,则创建 SINGLETON 对象
    • 更新 单子 ,因此它引用 当地人
    • 存储的指针 单子 进入之内 LOCALS['d']
    • 创建编号(300),即 顺便说一句,单人间。
    • 将指向这些数字(300)的指针存储到 d['a']
    • 因此 单子 也更新了。
    • 但是 当地人 更新了, 所以局部变量 a 局部变量['a'] 仍然是数字(1)
    • 现在, 本地人() 再次被调用, 单子 已更新。
    • AS D 单子 不是 当地人 , D 也有变化!

关于这个令人惊讶的细节,为什么 1 是单身汉吗? 300 不是,看 https://stackoverflow.com/a/306353

但请不要忘记:数字是不可变的,所以如果您试图将一个数字更改为另一个值,您就可以有效地创建另一个对象。

结论:

你不能带回来 执行程序 python 2到python 3的行为(通过更改代码除外),因为无法再更改程序流之外的局部变量。

但是,您可以将python 3的行为引入python 2,这样您就可以在今天编写运行相同的程序,而不管它们是与python3还是python2一起运行。这是因为在(更新的)python 2中可以使用 执行程序 对于类似函数的参数(实际上,这些参数是2或3元组),with允许使用与python 3中已知语义相同的语法:

exec "code"

(只在python 2中工作)变为(在python2和3中工作):

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

但要小心,那 "code" 不能再这样更改本地封闭范围。另请参见 https://docs.python.org/2/reference/simple_stmts.html#exec

最后几句话:

改变 执行程序 在python 3中很好。因为优化。

在python 2中,您无法在 执行程序 ,因为包含不可变内容的所有局部变量的状态可能会发生不可预测的更改。这不可能再发生了。现在,函数调用的一般规则适用于 exec() 和其他功能一样。

LRGH
Reply   •   3 楼
LRGH    8 年前

我想说这是一只蟒蛇。

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 当地的情况如下:

  • 如果变量设置在 执行程序 这个变量是一个局部变量,然后 执行程序 修改内部字典(返回的字典 locals() )不会使它回到原来的状态。呼叫 本地人() 更新字典(如python文档第2节所述),并在 执行程序 被遗忘了。 打电话的需要 本地人() 更新字典不是python3的bug,因为它有文档记录,但不直观。此外,事实上,在 执行程序 “不要更改函数的局部变量”是与python2有文档记录的差异(文档中说“如果在函数exec()返回后需要查看代码对局部变量的影响,请传递显式局部变量字典”),我更喜欢python2的行为。
  • 如果变量设置在 执行程序 这个变量以前不存在,然后 执行程序 修改内部字典,除非随后设置了变量。好像路上有个虫子 本地人() 更新字典;此错误允许访问 执行程序 通过呼叫 本地人() 之后 执行程序 .
Martijn Pieters
Reply   •   4 楼
Martijn Pieters    6 年前

两者之间有很大的区别 exec 在python 2和 exec() 在python 3中。你正在治疗 执行程序 作为函数,但它实际上是 陈述 在python 2中。

由于这种差异,不能使用 执行程序 ,即使在python 2中也是可能的。甚至不是以前声明的变量。

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

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

在python 2中,使用 执行程序 语句意味着编译器知道关闭本地范围优化(从 LOAD_FAST LOAD_NAME 例如,在本地和全局范围中查找变量)。用 执行() 作为函数,该选项不再可用,函数作用域现在 总是 优化。

而且,在python 2中, 执行程序 语句显式复制在中找到的所有变量 本地人() 返回函数局部变量使用 PyFrame_LocalsToFast ,但如果没有 全局变量 当地人 提供了参数。

正确的解决方法是为您的 执行() 呼叫:

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

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

注: 违约 当地人 按功能描述行事 本地人() 以下:对默认值的修改 当地人 不应尝试使用字典。传递显式 当地人 字典,如果需要查看代码对函数后局部变量的影响 执行() 返回。