该消息包含很多示例,时间有点长,但是我希望它能帮助我和其他人更好地掌握Python 2.7中变量和属性查找的全部内容。
我正在将PEP 227(http://www.python.org/dev/peps/pep-0227/)的术语用于代码块(例如模块,类定义,函数定义等)和变量绑定(例如作为赋值,参数声明,类和函数声明,for循环等)
我将术语变量用于可以不带点号调用的名称,将属性用于需要使用对象名称进行限定的名称(例如obj.x用于对象obj的属性x)。
Python中所有代码块都有三个作用域,但有以下功能:
Python中只有四个功能块(根据PEP 227):
将变量绑定到块中并找到它的规则非常简单:
让我知道一些验证此规则的示例,并显示了许多特殊情况。对于每个示例,我都会给出自己的理解。如果我错了,请纠正我。对于最后一个示例,我不了解结果。
范例1:
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:
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:
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:
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中的此语句“如果在代码块内的任何地方发生名称绑定操作,则该块内对该名称的所有使用都将视为对当前块的引用。” 当代码块是一个类时是错误的。而且,对于类来说,本地名称绑定似乎不是在编译时进行的,而是在执行期间使用类名称空间进行的。在这方面,PEP227和Python文档中的执行模型具有误导性,并且在某些方面是错误的。
范例5:
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。
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引用?
用两个词来说,示例5和示例6之间的区别在于,示例5中的变量x也被分配到相同的范围内,而示例6中没有。这触发了可以由历史原因理解的差异。
x
这引发了UnboundLocalError:
x = "foo" def f(): print x x = 5 f()
而不是打印“ foo”。即使一开始看起来很奇怪,这还是有道理的:函数f()在x本地定义了变量,即使它在打印之后也是如此,因此x在同一函数中对它的任何引用都必须是对该局部变量的引用。至少这是有道理的,如果您错误地在本地重用了全局变量的名称,并试图同时使用全局变量和局部变量,则可以避免意外的意外。这是一个好主意,因为这意味着我们可以静态地知道,只要看一眼变量, 这 意味着变量。例如,我们知道这里print x引用了局部变量(因此可能引发UnboundLocalError):
print x
x = "foo" def f(): if some_condition: x = 42 print x f()
现在,此规则不适用于类级范围:在那里,我们希望表达式x = x能够正常工作,将全局变量捕获x到类级范围中。这意味着类级别的作用域不遵循上面的基本规则:例如,我们不知道x该作用域中是指某个外部变量还是本地定义的x-–:
x = x
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的地方—仅在这种情况下— 而是在模块全局变量中查找。仅此而已:它不遵循嵌套作用域链。
为什么不?我实际上怀疑有一个更好的解释是“出于历史原因”。用更专业的术语来说,它可以认为该变量x既在类作用域中本地定义(因为已被分配给它), 又 应从父作用域作为词法嵌套变量传递(因为已被读取)。可以通过使用与LOAD_NAME在本地作用域中查找的字节码不同的字节码来实现它,如果未找到,则退回到使用嵌套作用域的引用。
LOAD_NAME
编辑: 感谢wilberforce对http://bugs.python.org/issue532860的引用。如果我们认为毕竟应该修复该问题,那么我们可能有机会用提议的新字节码重新进行一些讨论(错误报告考虑终止对它的支持,x = x但由于担心破坏太多现有代码而被关闭;相反,我是我建议这里将x = x在更多情况下进行工作)。否则我可能会错过另一个要点…
EDIT2: 似乎CPython在当前的3.4主干中确实做到了这一点:http : //bugs.python.org/issue17853 … …还是不?他们介绍字节码的原因略有不同,因此没有系统地使用它。