这个消息有点长,有很多例子,但我希望如此 将帮助我和其他人更好地掌握变量的全部故事 和Python 2.7中的属性查找。
我正在使用PEP 227的条款 (http://www.python.org/dev/peps/pep-0227/)用于代码块(例如 模块,类定义,函数定义等)和 变量绑定(例如赋值,参数声明,类 和函数声明,for循环等。)
我使用名称变量来表示可以在没有a的情况下调用的名称 dot,以及需要使用对象限定的名称的属性 name(例如对象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)和函数 如果不使用a,则类无法访问类的属性 限定名称(本例中为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引用 最内在的功能?
答案 0 :(得分:21)
在一个理想的世界里,你是对的,你发现的一些不一致是错的。但是,CPython已经优化了一些场景,特别是功能本地。这些优化,以及编译器和评估循环如何相互作用以及历史先例,都会导致混淆。
Python将代码转换为字节码,然后由解释器循环解释。 &#39;常规&#39;用于访问名称的操作码是LOAD_NAME
,它在字典中查找变量名称。 LOAD_NAME
首先将名称视为本地名称,如果失败,则查找全局名称。如果找不到名称,LOAD_NAME
会抛出NameError
个异常。
对于嵌套作用域,使用闭包实现查找当前作用域之外的名称;如果未分配名称但在嵌套(非全局)作用域中可用,则此类值将作为闭包处理。这是必需的,因为父作用域可以在不同的时间为给定名称保存不同的值;对父函数的两次调用可能导致不同的闭包值。所以Python对于那种情况有LOAD_CLOSURE
,MAKE_CLOSURE
和LOAD_DEREF
操作码;前两个操作码用于加载和创建嵌套作用域的闭包,LOAD_DEREF
将在嵌套作用域需要时加载闭合值。
现在,LOAD_NAME
相对较慢;它将参考两个字典,这意味着它必须首先对密钥进行散列并运行一些相等的测试(如果名称没有被实现)。如果名称不是本地名称,则必须再次为全局名称执行此操作。对于可能被称为成千上万次的函数,这可能会很快变得乏味。所以函数locals有特殊的操作码。加载本地名称由LOAD_FAST
实现,它在特殊的本地名称数组中按索引查找本地变量。这要快得多,但它确实要求编译器首先要查看名称是否是本地名称而不是全局名称。为了仍然能够查找全局名称,使用了另一个操作码LOAD_GLOBAL
。编译器显式优化此情况以生成特殊操作码。如果名称没有值,LOAD_FAST
将抛出UnboundLocalError
异常。
类定义体虽然它们被视为一个函数,但是没有得到这个优化步骤。类定义并不意味着经常被调用;导入时,大多数模块都会创建类。嵌套时类范围也不算,因此规则更简单。因此,当您开始稍微调整范围时,类定义主体不会像函数那样起作用。
因此,对于非函数作用域,LOAD_NAME
和LOAD_DEREF
分别用于locals和globals以及闭包。对于函数,使用LOAD_FAST
,LOAD_GLOBAL
和LOAD_DEREF
代替。
请注意,只要Python执行class
行,就会执行类主体!因此,在示例1中,class B
内的class A
会在class A
执行后立即执行,即导入模块时。在示例2中,C
在调用f()
之前不会执行,而不是之前。
让我们来看看你的例子:
您已在类A.B
中嵌套了一个类A
。类主体不构成嵌套作用域,因此即使在执行类A.B
时执行A
类主体,编译器也将使用LOAD_NAME
来查找x
。 A.B().f()
是函数(作为方法绑定到B()
实例),因此它使用LOAD_GLOBAL
加载x
。我们会在此处忽略属性访问,这是一个非常明确的名称模式。
此处f().C.z
位于类范围内,因此函数f().C().g()
将跳过C
范围,并使用{{1来查看f()
范围}}
此处LOAD_DEREF
被编译器确定为本地,因为您在范围内分配了它。函数已经过优化,因此var
用于查找本地并抛出异常。
现在事情变得有些奇怪了。 LOAD_FAST
在类范围内执行,因此正在使用class A
。已从范围的本地字典中删除LOAD_NAME
,因此第二次访问A.x
会导致找到全局x
; x
首先寻找当地人,但没有在那里找到它,回到全球查询。
是的,这似乎与文档不一致。 Python-the-language和CPython-实现在这里发生了一些冲突。但是,你正在用动态语言推动可能和实际的界限;检查LOAD_NAME
是否应该是x
中的本地文件是可能的,但需要花费宝贵的执行时间来处理大多数开发人员永远不会遇到的问题。
现在你混淆了编译器。您在类范围中使用了LOAD_NAME
,因此您要从范围之外的名称设置 local 。编译器发现x = x
在这里是一个本地(你分配给它),因此它从不认为它也可以是一个范围名称。编译器在此范围内对x
的所有引用使用LOAD_NAME
,因为这不是优化的函数体。
执行类定义时,x
首先要求您查找x = x
,因此它使用x
来执行此操作。未定义LOAD_NAME
,x
找不到本地,因此找到了全局 LOAD_NAME
。结果值存储为本地,发生也称为x
。 x
再次使用print x
,现在找到新的本地LOAD_NAME
值。
这里你没有混淆编译器。您正在创建本地x
,y
不是本地的,因此编译器将其识别为父函数x
的范围名称。从关闭位置f2().myfunc()
查找x
,并存储在LOAD_DEREF
。
你可以看到5到6之间的混淆是一个错误,尽管在我看来这个错误并不值得修复。它当然是这样提交的,请参阅Python bug跟踪器中的issue 532860,它已存在超过10年了。
对于示例5中的第一个分配,即使y
也本地,编译器也可以检查范围名称x
。或者x
可以检查名称是否真的是本地名称,如果没有找到本地名称则抛出LOAD_NAME
,但代价是性能更高。如果它位于函数范围内,则UnboundLocalError
将用于示例5,并且会立即抛出LOAD_FAST
。
但是,正如引用的错误所示,由于历史原因,行为仍然保留。
。今天可能有代码可以解决这个错误。答案 1 :(得分:18)
用两个词来说,示例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
在更多情况下工作)。或者我可能会错过另一个好点...
EDIT2:似乎CPython正好在当前的3.4主干中做到了:http://bugs.python.org/issue17853 ...或不?他们引入了字节码的原因略有不同,并没有系统地使用它......
答案 2 :(得分:6)
长话短说,这是Python范围的一个极端情况,有点不一致,但必须保持向后兼容性(并且因为它不清楚正确答案应该是什么)。在实施PEP 227时,您可以在Python邮件列表上看到大量关于它的original discussion,以及bug中有一些关于此行为的修复。
我们可以解决为什么使用dis
模块存在差异,这使我们可以查看代码对象内部以查看已编译的代码片段的字节码。我使用的是Python 2.6,所以这个细节可能略有不同 - 但我看到了相同的行为,所以我认为它可能接近2.7。
初始化每个嵌套MyClass
的代码存在于一个代码对象中,您可以通过顶级函数的属性获取该代码对象。 (我正在将示例5和示例6中的函数分别重命名为f1
和f2
。)
代码对象有一个co_consts
元组,其中包含myfunc
代码对象,后者又包含在创建MyClass
时运行的代码:
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]
然后,您可以使用dis.dis
:
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
唯一的区别是,在MyClass1
中,x
使用LOAD_NAME
操作符加载,而在MyClass2
中,使用LOAD_DEREF
加载。 LOAD_DEREF
在封闭范围内查找名称,因此它在myfunc中获取'x'。 LOAD_NAME
不遵循嵌套作用域 - 因为它无法看到x
或myfunc
中绑定的f1
名称,所以它获得了模块级绑定。
然后问题是,为什么两个版本的MyClass
的代码被编译为两个不同的操作码?在f1
中,绑定在类范围内隐藏x
,而在f2
中绑定新名称。如果MyClass
范围是嵌套函数而不是类,则y = x
中的f2
行将编译相同,但x = x
中的f1
将是LOAD_FAST
- 这是因为编译器会知道函数中绑定了x
,所以它应该使用LOAD_FAST
来检索局部变量。在调用它时,这将失败UnboundLocalError
。
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
此操作失败,因为MyFunc
功能随后使用LOAD_FAST
:
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
(顺便说一下,对于函数中类和函数中的代码与范围内的代码交互方式应该有所区别,这并不奇怪。你可以告诉它,因为类级别的绑定不可用在方法中 - 方法范围不像嵌套函数那样嵌套在类范围内。您必须通过类或使用self.
显式地访问它们(如果有的话,它将回退到类中)不是实例级绑定)。)