How references to variables are resolved in Python
这个消息有很多例子,有点长,但我希望如此将帮助我和其他人更好地掌握变量的完整故事以及python 2.7中的属性查找。
我用的是PEP 227的术语(http://www.python.org/dev/peps/pep-0227/)用于代码块(例如模块、类定义、功能定义等)和变量绑定(如赋值、参数声明、类和函数声明,用于循环等)
我使用变量这个术语来命名那些不需要需要用对象限定的名称的点和属性名称(例如对象obj的属性x的obj.x)。
在python中,所有代码块都有三个作用域,但是函数:
- 局部的
- 全球的
- 内建
在python中,只有四个块用于函数(根据PEP 227):
- 局部的
- 封闭函数
- 全球的
- 内建
将变量绑定到块并在块中查找变量的规则是很简单:
- 变量与块中对象的任何绑定都会使该变量除非变量声明为全局变量(在如果变量属于全局范围)
- 使用规则lgb(local,全局,内置)用于所有块,但函数除外
- 使用规则legb(local,封闭、全局、内置)仅用于函数。
让我知道,举个例子来验证这个规则,并展示许多特殊情况。对于每个例子,我将给出我的理解。拜托如果我错了就纠正我。最后一个例子,我不理解结果。
例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | x ="x in module" class A(): print"A:" + x #x in module x ="x in class A" print locals() class B(): print"B:" + x #x in module x ="x in class B" print locals() def f(self): print"f:" + x #x in module self.x ="self.x in f" print x, self.x print locals() >>>A.B().f() A: x in module {'x': 'x in class A', '__module__': '__main__'} B: x in module {'x': 'x in class B', '__module__': '__main__'} f: x in module x in module self.x in f {'self': <__main__.B instance at 0x00000000026FC9C8>} |
类(规则lgb)和中的函数没有嵌套范围如果不使用限定名(本例中为self.x)。这在PEP227
例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 | z ="z in module" def f(): z ="z in f()" class C(): z ="z in C" def g(self): print z print C.z C().g() f() >>> z in f() z in C |
这里函数中的变量是使用legb规则查找的,但是如果类在路径中,将跳过类参数。再来一次,这就是PEP 227所解释的。
例3:
1 2 3 4 5 6 7 8 9 10 11 12 | var = 0 def func(): print var var = 1 >>> func() Traceback (most recent call last): File"<pyshell#102>", line 1, in <module> func() File"C:/Users/aa/Desktop/test2.py", line 25, in func print var UnboundLocalError: local variable 'var' referenced before assignment |
我们期望有一种动态语言,如python,一切都是动态解决。但对于函数来说,情况并非如此。局部的变量在编译时确定。PEP 227和http://docs.python.org/2.7/reference/executionmodel.html对此进行了描述这样的行为
"如果名称绑定操作发生在代码块内的任何位置,则所有块中名称的使用被视为对当前块。"
例4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | x ="x in module" class A(): print"A:" + x x ="x in A" print"A:" + x print locals() del x print locals() print"A:" + x >>> A: x in module A: x in A {'x': 'x in A', '__module__': '__main__'} {'__module__': '__main__'} A: x in module |
但我们在这里看到,这个声明在PEP227"如果一个名字绑定操作发生在代码块内的任何位置,所有名称的使用在块内被视为对当前块的引用。"is当代码块是类时出错。而且,对于班级来说本地名称绑定不是在编译时进行的,而是在使用类命名空间执行。在这方面,PEP227和python文档中的执行模型具有误导性,并且有些部分出错。
例5:
1 2 3 4 5 6 7 8 9 10 11 12 13 | x = 'x in module' def f2(): x = 'x in f2' def myfunc(): x = 'x in myfunc' class MyClass(object): x = x print x return MyClass myfunc() f2() >>> x in module |
我对这段代码的理解如下。指令x=x首先查找表达式右侧x引用的对象去。在这种情况下,对象在类中本地查找,然后根据规则lgb,它在全局范围内查找,即字符串"x in module"。那么myClass的本地属性x是在类字典中创建并指向字符串对象。
例6:
下面是一个我无法解释的例子。它非常接近示例5,我只是在更改本地myclass属性从x到y。
1 2 3 4 5 6 7 8 9 10 11 12 13 | x = 'x in module' def f2(): x = 'x in f2' def myfunc(): x = 'x in myfunc' class MyClass(object): y = x print y return MyClass myfunc() f2() >>> x in myfunc |
为什么在这种情况下,myClass中的x引用在最里面的功能?
在一个理想的世界里,你是对的,你发现的一些矛盾是错误的。然而,cpython优化了一些场景,特别是函数局部变量。这些优化,连同编译器和评估循环如何交互以及历史先例,都会导致混淆。好的。
python将代码转换为字节码,然后由解释器循环解释。访问名称的"常规"操作码是
对于嵌套作用域,使用闭包实现查找当前作用域之外的名称;如果名称未分配给但在嵌套(非全局)作用域中可用,则将此类值作为闭包处理。这是必需的,因为父作用域可以在不同的时间为给定的名称保存不同的值;对父函数的两个调用可能导致不同的结束值。所以python有针对这种情况的
现在,
另一方面,类定义体虽然被视为一个函数,但并没有得到这个优化步骤。类定义并不意味着经常被调用;大多数模块在导入时只创建一次类。嵌套时类作用域也不计算,因此规则更简单。因此,当您开始稍微混合作用域时,类定义主体不会像函数那样工作。好的。
因此,对于非功能范围,
注意,类体在python执行
让我们来看看你的例子:好的。
您已经将类
这里
这里
现在事情变得有点奇怪了。
是的,这似乎与文档不一致。python语言和cpython实现在这里有点冲突。但是,您正在推动动态语言中可能的和实际的边界;检查
现在您混淆了编译器。您在类作用域中使用了
在执行类定义时,
这里您没有混淆编译器。您正在创建一个本地
你可以把5和6之间的混淆看作一个bug,尽管在我看来这不值得修正。它当然是这样归档的,参见第532860期的python bug tracker,它已经存在了10多年了。好的。
编译器可以检查作用域名称
但是,正如引用的bug所示,出于历史原因,行为被保留。如果修复了这个错误,今天可能有代码会被破解。好的。好啊。
换句话说,例5和例6之间的区别在于,在例5中,变量
这将引发UnboundLocalError:
1 2 3 4 5 | x ="foo" def f(): print x x = 5 f() |
而不是打印"foo"。这有点道理,即使一开始看起来很奇怪:函数f()在本地定义变量
1 2 3 4 5 6 | x ="foo" def f(): if some_condition: x = 42 print x f() |
现在,这个规则不适用于类级作用域:在这里,我们希望像
1 2 3 4 5 6 7 8 9 10 11 12 13 | class X: x = x # we want to read the global x and assign it locally bar = x # but here we want to read the local x of the previous line class Y: if some_condition: x = 42 print x # may refer to either the local x, or some global x class Z: for i in range(2): print x # prints the global x the 1st time, and 42 the 2nd time x = 42 |
因此,在类范围中,使用了一个不同的规则:它通常会引发UnboundLocalError——并且仅在这种情况下才会在模块全局中查找。仅此而已:它不遵循嵌套范围链。
为什么不?实际上,我怀疑有更好的解释"出于历史原因"。在更专业的术语中,它可以认为变量
编辑:感谢Wilberforce提供对http://bugs.python.org/issue532860的参考。如果我们认为新的字节码终究应该被修复,那么我们可能有机会重新进行一些讨论(bug报告考虑取消对
edit2:似乎cpython在当前的3.4主干中做到了:http://bugs.python.org/issue17853…或不是?他们引入字节码的原因略有不同,并且没有系统地使用它…
长话短说,这是Python范围界定的一个小例子,有点不一致,但为了向后兼容必须保持(因为还不清楚正确的答案应该是什么)。在执行PEP 227时,您可以在python邮件列表中看到很多关于它的原始讨论,并且在bug中可以看到一些这样的行为是可以修复的。
我们可以使用
初始化每个嵌套
code对象有一个
1 2 3 4 5 6 7 8 | In [20]: f1.func_code.co_consts Out[20]: (None, 'x in f2', <code object myfunc at 0x1773e40, file"<ipython-input-3-6d9550a9ea41>", line 4>) In [21]: myfunc1_code = f1.func_code.co_consts[2] In [22]: MyClass1_code = myfunc1_code.co_consts[3] In [23]: myfunc2_code = f2.func_code.co_consts[2] In [24]: MyClass2_code = myfunc2_code.co_consts[3] |
然后您可以在使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | In [25]: from dis import dis In [26]: dis(MyClass1_code) 6 0 LOAD_NAME 0 (__name__) 3 STORE_NAME 1 (__module__) 7 6 LOAD_NAME 2 (x) 9 STORE_NAME 2 (x) 8 12 LOAD_NAME 2 (x) 15 PRINT_ITEM 16 PRINT_NEWLINE 17 LOAD_LOCALS 18 RETURN_VALUE In [27]: dis(MyClass2_code) 6 0 LOAD_NAME 0 (__name__) 3 STORE_NAME 1 (__module__) 7 6 LOAD_DEREF 0 (x) 9 STORE_NAME 2 (y) 8 12 LOAD_NAME 2 (y) 15 PRINT_ITEM 16 PRINT_NEWLINE 17 LOAD_LOCALS 18 RETURN_VALUE |
所以唯一的区别是在
那么问题是,为什么两个版本的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | In [28]: x = 'x in module' def f3(): x = 'x in f2' def myfunc(): x = 'x in myfunc' def MyFunc(): x = x print x return MyFunc() myfunc() f3() --------------------------------------------------------------------------- Traceback (most recent call last) <ipython-input-29-9f04105d64cc> in <module>() 9 return MyFunc() 10 myfunc() ---> 11 f3() <ipython-input-29-9f04105d64cc> in f3() 8 print x 9 return MyFunc() ---> 10 myfunc() 11 f3() <ipython-input-29-9f04105d64cc> in myfunc() 7 x = x 8 print x ----> 9 return MyFunc() 10 myfunc() 11 f3() <ipython-input-29-9f04105d64cc> in MyFunc() 5 x = 'x in myfunc' 6 def MyFunc(): ----> 7 x = x 8 print x 9 return MyFunc() UnboundLocalError: local variable 'x' referenced before assignment |
由于
1 2 3 4 5 6 7 8 9 10 11 | In [31]: myfunc_code = f3.func_code.co_consts[2] MyFunc_code = myfunc_code.co_consts[2] In [33]: dis(MyFunc_code) 7 0 LOAD_FAST 0 (x) 3 STORE_FAST 0 (x) 8 6 LOAD_FAST 0 (x) 9 PRINT_ITEM 10 PRINT_NEWLINE 11 LOAD_CONST 0 (None) 14 RETURN_VALUE |
(顺便说一句,范围界定与类主体中的代码和函数中的代码之间的交互方式应该有所不同,这并不奇怪。您可以这样说,因为类级别的绑定在方法中不可用-方法作用域没有像嵌套函数那样嵌套在类作用域中。您必须通过类或使用