这条消息有点长,例子很多,但我希望它
将帮助我和其他人更好地掌握变量的完整故事
和 Python 2.7 中的属性查找。
我正在使用 PEP 227 的条款
( http://www.python.org/dev/peps/pep-0227/ ) 用于代码块(例如
模块、类定义、函数定义等)和
变量绑定(bind)(例如赋值、参数声明、类
和函数声明、for 循环等)
我正在使用术语变量作为可以在没有名称的情况下调用的名称
点,以及需要用对象限定的名称的属性
名称(例如对象 obj 的属性 x 的 obj.x)。
Python 中的所有代码块都有三个作用域,但是函数:
Python 中有四个块仅用于函数(根据
PEP 227):
将变量绑定(bind)到并在块中找到它的规则是
非常简单:
局部于这个块,除非变量被声明为全局的(在那个
如果变量属于全局范围)
global, builtin) 用于所有块,但函数
仅用于函数的封闭、全局、内置)。
让我知道举个例子来验证这个规则,并展示许多
特别案例。对于每个例子,我都会给出我的理解。请
如果我错了,请纠正我。对于最后一个例子,我不明白
结果。
示例 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描述这个
这样的行为
“如果名称绑定(bind)操作发生在代码块内的任何地方,所有
块内名称的使用被视为对
当前区块。”
示例 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 中的这条语句“如果名称绑定(bind)
操作发生在代码块内的任何地方,名称的所有使用
块内被视为对当前块的引用。”是
当代码块是一个类时出错。此外,对于类,似乎
本地名称绑定(bind)不是在编译时进行的,而是在
使用类命名空间执行。在这方面,
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 中没有。这引发了可以通过历史原因理解的差异。
这会引发 UnboundLocalError:
x = "foo"
def f():
print x
x = 5
f()
而不是打印“foo”。这有点道理,即使起初看起来很奇怪:函数 f() 定义了变量
x
本地,即使是在打印之后,因此对 x
的任何引用在同一个函数中必须指向那个局部变量。至少它是有道理的,因为如果您错误地在本地重用了全局变量的名称,并且试图同时使用全局变量和局部变量,它可以避免奇怪的惊喜。这是一个好主意,因为这意味着我们可以静态地知道,仅仅通过查看一个变量,它意味着哪个变量。例如,我们知道 print x
这里指的是局部变量(因此可能会引发 UnboundLocalError):x = "foo"
def f():
if some_condition:
x = 42
print x
f()
现在,这条规则不适用于类级作用域:在那里,我们需要像
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
不同的字节码来实现它。在本地范围内查找,如果找不到,则回退到使用嵌套范围的引用。编辑:感谢 wilberforce 对 http://bugs.python.org/issue532860 的引用.我们可能有机会通过提议的新字节码重新激活一些讨论,如果我们认为它毕竟应该被修复(错误报告考虑终止对
x = x
的支持,但由于担心破坏太多现有代码而被关闭;相反我在这里建议的是让 x = x
在更多情况下工作)。或者我可能错过了另一个好点......编辑2:似乎 CPython 在当前的 3.4 主干中做到了这一点:http://bugs.python.org/issue17853 ... 或不?他们引入字节码的原因略有不同,并且没有系统地使用它......
关于python - Python 中如何解析对变量的引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20246523/