总而言之:Python 2和Python 3中都没有bug

exec的不同行为源于exec在Python 2中是一个语句,而在Python 3中是一个函数。Please note:

I do not tell anything new here. This is just an assembly of the truth

out there found in all the other answers and comments.

All I try here is to bring light to some of the more obscure details.

Python 2和Python 3之间的唯一区别是,实际上,exec能够更改python2中封闭函数的本地作用域(因为它是一个语句,可以访问当前的本地作用域),并且在python3中无法再这样做(因为它现在是一个函数,所以在它自己的本地作用域中运行)。

然而,这种恼怒与exec语句无关,它只源于一个特殊的行为细节:

locals()返回一些内容,我想将其称为“一个在调用locals()之后始终只引用本地作用域中所有变量的按作用域可变的单例”。

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

“在局部作用域中引用变量的作用域可变单例”是什么意思?它是一个scope-wise singleton,因为无论您在同一范围内调用locals()的频率如何,返回的对象总是相同的。

因此,观察结果是,id(d) == id(locals()),因为d和locals()指的是同一个对象,同一个单例,因为只能有一个(在不同的作用域中,您得到不同的对象,但在同一作用域中,您只能看到这个单例)。

它是mutable,因为它是一个普通对象,所以您可以更改它。

locals()强制对象中的所有条目再次引用本地作用域中的变量。

如果您更改了对象中的某些内容(通过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)-这是另一个单例,顺便说一句

将指向此数字(1)的指针存储到LOCALS['a']

(其中LOCALS应为内部本地范围)

如果不存在,则创建SINGLETON对象

更新SINGLETON,因此它引用LOCALS中的所有条目

将SINGLETON的指针存储到LOCALS['d']

创建一个数字(300),它是而不是一个单子,顺便说一句

将指向这些数字(300)的指针存储到d['a']

因此,SINGLETON也被更新。

但是LOCALS没有更新,

所以局部变量a或LOCALS['a']仍然是数字(1)

现在,再次调用locals(),更新SINGLETON。

因为d指的是SINGLETON,而不是LOCALS,所以d也会发生变化!For more on this surprising detail, why 1 is a singleton while 300 is not, see https://stackoverflow.com/a/306353

But please do not forget: Numbers are immutable, so if you try to change a number to another value, you effectively create another object.

结论:

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

但是,您可以将Python 3的行为引入到Python 2中,这样您现在就可以编写运行相同的程序,而不管它们是使用python3还是python2运行。这是因为在(较新的)Python 2中,还可以将exec与类似函数的参数一起使用(实际上,这些参数是2或3元组),并允许使用具有与Python3相同语义的ame语法:exec "code"

(仅适用于Python2)变成(适用于Python2和3):exec("code", globals(), locals())

最后几句话:

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

在Python 2中,无法跨exec进行优化,因为包含不可变内容的所有局部变量的状态都可能发生不可预知的更改。这再也不会发生了。现在,函数调用的一般规则也适用于exec(),就像所有其他函数一样。