在Python中如何引用variables

这个消息有很多例子,但是我希望它能帮助我和其他人更好地理解Python 2.7中variables和属性查找的全部内容。

我正在使用PEP 227( http://www.python.org/dev/peps/pep-0227/ )中的代码块(如模块,类定义,函数定义等)和variables绑定(例如作为赋值,参数声明,类和函数声明,for循环等)

我使用的术语variables的名称,可以被称为没有点,名称的属性需要与对象名称限定(如obj.x为对象obj的属性x)。

在Python中有三个代码块的作用域,但是函数:

  • 本地
  • 全球
  • 内build

Python中只有四个块用于function(根据PEP 227):

  • 本地
  • 围绕function
  • 全球
  • 内build

将variables绑定到块中并find它的规则非常简单:

  • 除非variables被声明为全局variables(在这种情况下variables属于全局variables),否则variables与块中某个对象的任何绑定都会使该variables局部于该块中,
  • 对所有块使用规则LGB(local,global,builtin)查找对variables的引用,但是函数
  • 只有函数使用规则LEGB(local,enclosing,global,builtin)来查找对variables的引用。

举个例子来validation这个规则,并且展示很多特例。 对于每个例子,我会给我的理解。 如果我错了,请纠正我。 对于最后一个例子,我不了解结果。

例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() >>>AB().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 Cz C().g() f() >>> z in f() z in C 

这里使用LEGB规则查找函数中的variables,但是如果类在path中,则类参数将被跳过。 这也是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之类的dynamic语言来dynamic地解决所有问题。 但function并非如此。 局部variables是在编译时确定的。 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中的这个语句“如果一个名字绑定操作发生在一个代码块中的任何地方,那么该块中名字的所有用法都被视为对当前块的引用。 当代码块是一个类时是错误的。 而且,对于类,似乎本地名称绑定不是在编译时进行的,而是在使用类名称空间执行期间进行的。 在这方面,Python文档中的PEP227和执行模型是误导性的,对于某些部分是错误的。

例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首先查找expression式的右边x所指向的对象。 在这种情况下,在类中本地查找对象,然后按照规则LGB在全局范围内查找,即string“模块中的x”。 然后在类字典中创build一个到MyClass的本地属性x,并指向string对象。

例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中,variablesx也被分配到了相同的范围内,而不是在例6中。这触发了一个可以被历史原因理解的差异。

这引发了UnboundLocalError:

 x = "foo" def f(): print x x = 5 f() 

而不是打印“foo”。 它有点意义,即使它起初看起来很奇怪:函数f()在本地定义variablesx ,即使它在打印之后,所以在同一函数中对x任何引用必须是该局部variables。 至less有意义的是,如果你错误地在本地重新使用了一个全局variables的名字,并且试图同时使用全局variables和局部variables,它就避免了奇怪的感觉。 这是一个好主意,因为这意味着我们可以通过查看一个variables静态地知道它意味着哪个variables。 例如,我们知道print x引用了局部variables(因此可能会引发UnboundLocalError):

 x = "foo" def f(): if some_condition: x = 42 print x f() 

现在,这个规则不适用于类级作用域:在那里,我们希望像x = x这样的expression式工作,将全局variablesx捕获到类级作用域中。 这意味着类级别的作用域不遵循上面的基本规则:我们不知道这个作用域中的x是指外部variables还是指向本地定义的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 —在这种情况下—它在模块全局variables中查找。 这就是全部:它不遵循嵌套作用域链。

为什么不? 我怀疑是否有一个更好的解释是“出于历史原因”。 更技术性地说,它可以认为variablesx在类范围中是本地定义的(因为它被赋值), 并且应该从父范围作为词汇嵌套variables传递(因为它被读取)。 可以通过使用与在本地范围内查找的LOAD_NAME不同的字节码来实现它,并且如果找不到则使用嵌套范围的引用。

编辑:感谢wilberforce参考http://bugs.python.org/issue532860 。 如果我们认为它应该被修复,那么我们可能有机会得到一些与新build立的字节码重新激活的讨论(错误报告考虑杀死对x = x支持,但由于害怕破坏太多现有的代码而closures)我在这里提出的build议是让x = x在更多的情况下工作)。 或者我可能会错过另一个优点

编辑2:似乎CPython正是在目前的3.4主干: http : //bugs.python.org/issue17853 …或不? 他们介绍了字节码的原因稍有不同,不要系统地使用它…

在一个理想的世界里,你是对的,你发现的一些不一致是错误的。 但是,CPython已经优化了一些场景,特别是当地的function。 这些优化以及编译器和评估循环如何相互作用和历史先例导致了混淆。

Python将代码转换为字节码,然后由解释器循环解释。 用于访问名称的“常规”操作码是LOAD_NAME ,它会像在字典中那样查找variables名称。 LOAD_NAME将首先查找本地名称,如果失败,则查找全球名称。 找不到名称时, LOAD_NAME将引发NameErrorexception。

对于嵌套的作用域,查找当前作用域之外的名称是使用闭包实现的; 如果一个名字没有被赋值,但是在一个嵌套的(不是全局的)作用域中是可用的,那么这些值将作为一个闭包来处理。 这是必要的,因为父范围可以在不同的时间为给定的名称保存不同的值; 对父函数的两个调用可能导致不同的闭包值。 所以Python对于这种情况有LOAD_CLOSUREMAKE_CLOSURELOAD_DEREF操作码; 前两个操作码用于加载和创build嵌套范围的闭包,当嵌套范围需要时, LOAD_DEREF将加载闭包值。

现在, LOAD_NAME比较慢, 它会查询两个字典,这意味着它必须首先散列键,然后运行一些相等性testing(如果名字没有被限制)。 如果名称不是本地的,那么它必须再次为全球做。 对于可能被称为成千上万次的函数,这可能会很快乏味。 所以function本地人有特殊的操作码。 加载本地名称由LOAD_FAST实现, LOAD_FAST 通过索引在特定的本地名称数组中查找局部variables。 这要快得多,但它确实要求编译器首先必须查看名称是否是本地名称而不是全局名称。 为了仍然能够查找全局名称,使用另一个操作码LOAD_GLOBAL 。 编译器明确地优化这种情况来生成特殊的操作码。 当名称没有值时, LOAD_FAST会抛出一个UnboundLocalErrorexception。

类定义体,另一方面,虽然他们被视为很像一个函数,不要得到这个优化的一步。 类定义并不意味着经常被调用; 大多数模块在导入时会创build一次类。 当嵌套时,类作用域不计算,所以规则更简单。 因此,当你开始混合一些示波器时,类定义体不会像函数那样起作用。

因此,对于非函数作用域, LOAD_NAMELOAD_DEREF分别用于本地和全局,以及用于closures。 对于函数,则使用LOAD_FASTLOAD_GLOBALLOAD_DEREF

请注意,只要Python执行class行,就会执行class ! 因此,在示例1中, class B class A class B class A执行了class A ,就会在您导入模块时立即执行。 在例2中,直到f()被调用, C才被执行,而不是之前。

让我们来看看你的例子:

  1. 您已经在A类中嵌套了AB类。 类体不构成嵌套的作用域,所以即使在类A执行时执行了AB类体,编译器也将使用LOAD_NAME来查找xAB().f()是一个函数 (作为方法绑定到B()实例),所以它使用LOAD_GLOBAL来加载x 。 我们将在这里忽略属性访问,这是一个非常明确的名称模式。

  2. 这里f().Cz是在类范围内,所以函数f().C().g()会跳过C范围,而使用LOAD_DEREF来查看f()范围。

  3. 这里var被编译器确定为本地的,因为你在范围内赋值了它。 函数被优化,所以LOAD_FAST被用来查找本地,并抛出一个exception。

  4. 现在事情变得有点怪异。 class A在类作用域执行,因此正在使用LOAD_NAMEAx从该范围的本地语言字典中被删除,所以第二次访问x导致find全局xLOAD_NAME首先find了本地,并没有在那里find,回到全球查找。

    是的,这似乎与文档不一致。 Python-the-language和CPython-实现在这里有点冲突。 然而,你正在以dynamic的语言推动可能的和实际的界限。 检查x是否应该是LOAD_NAME的本地代码将是可能的,但是对于大多数开发人员永远不会遇到的angular落案例来说,需要花费宝贵的执行时间。

  5. 现在你混淆了编译器。 您在类作用域中使用了x = x ,因此您正在从作用域之外的名称设置本地 。 编译器发现x是本地的(你赋值给它的),所以它永远不会认为它也可以是作用域名。 编译器在此范围内使用LOAD_NAME来引用x ,因为这不是优化的函数体。

    在执行类定义时, x = x首先需要查找x ,所以它使用LOAD_NAME来执行此操作。 没有定义xLOAD_NAME没有find本地,所以find全局 x 。 结果值存储为本地,也恰好被命名为xprint x再次使用LOAD_NAME ,现在find新的本地x值。

  6. 在这里你不会混淆编译器。 你正在创build一个本地yx不是本地的,所以编译器将它识别为来自父函数f2().myfunc()的作用域名称。 x从closures中用LOAD_DEREF ,并存储在y

你可以看到5和6之间的混淆作为一个错误,尽pipe在我看来这是不值得修复的。 当然,如果是这样的话,请参阅Python bug跟踪器中的问题532860 ,它已经存在了10多年了。

编译器可以检查范围名称x即使x 也是本地的,例如第五个例子中的第一个任务。或者LOAD_NAME可以检查名称是否是本地的,真的,如果没有find本地的话就抛出一个UnboundLocalError以牺牲更多的性能为代价。 如果已经在函数范围内, LOAD_FAST将被用于例子5,并且立即抛出一个UnboundLocalError

但是,正如所引用的错误所示,由于历史原因,行为被保留。 今天可能有代码,这个bug会被修复。

长话短说,这是Python的范围界定的一个有点不一致的情况,但必须保持向后兼容性(因为不清楚正确的答案应该是什么)。 当PEP 227被实现的时候,你可以在Python邮件列表上看到很多关于它的原始讨论 ,还有一些是在这个问题上修复的。

我们可以计算出为什么使用dis模块有所不同,它让我们查看代码对象的内部来查看一段代码已经被编译到的字节码。 我使用的是Python 2.6,所以细节可能略有不同 – 但我看到了相同的行为,所以我认为它可能接近2.7。

初始化每个嵌套MyClass的代码位于一个代码对象中,您可以通过顶级函数的属性获取该代码对象。 (我将函数从例5和例6分别重命名为f1f2 。)

代码对象有一个co_consts元组,其中包含myfunc代码对象,该代码对象又具有创buildMyClass时运行的代码:

 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 

所以唯一的区别是在MyClass1x使用LOAD_NAME op加载,而在MyClass2使用LOAD_DEREF加载。 LOAD_DEREF在封闭范围内查找一个名字,所以它在'myfunc'中得到'x'。 LOAD_NAME不遵循嵌套的作用域 – 因为它看不到在myfuncf1绑定的x名称,所以它获得了模块级别的绑定。

那么问题是,为什么两个MyClass版本的代码被编译成两个不同的操作码? 在f1 ,绑定是在类范围中映射x的,而在f2则绑定了一个新名称。 如果MyClass范围是嵌套函数而不是类,那么f2y = x行将被编译为相同的,但是f1x = x将是LOAD_FAST – 这是因为编译器会知道x被绑定在函数中,所以它应该使用LOAD_FAST来检索一个局部variables。 调用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.来显式地到达它们(如果没有实例级别的话,它将回退到类中捆绑)。)