小编典典

如何在Python中解析对变量的引用

python

该消息包含很多示例,时间有点长,但是我希望它能帮助我和其他人更好地掌握Python 2.7中变量和属性查找的全部内容。

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

我将术语变量用于可以不带点号调用的名称,将属性用于需要使用对象名称进行限定的名称(例如obj.x用于对象obj的属性x)。

Python中所有代码块都有三个作用域,但有以下功能:

  • 本地
  • 全球
  • 内建

Python中只有四个功能块(根据PEP 227):

  • 本地
  • 封闭功能
  • 全球
  • 内建

将变量绑定到块中并找到它的规则非常简单:

  • 变量与块中对象的任何绑定都使该变量在此块中成为局部变量,除非将该变量声明为全局变量(在这种情况下,该变量属于全局范围)
  • 对于所有块,使用规则LGB(本地,全局,内置)查找对变量的引用,但函数
  • 仅使用LEGB规则(局部,封闭,全局,内置)查找对变量的引用。

让我知道一些验证此规则的示例,并显示了许多特殊情况。对于每个示例,我都会给出自己的理解。如果我错了,请纠正我。对于最后一个示例,我不了解结果。

范例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引用?


阅读 243

收藏
2020-12-20

共1个答案

小编典典

用两个词来说,示例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 …
…还是不?他们介绍字节码的原因略有不同,因此没有系统地使用它。

2020-12-20