我们经常听说Python函数访问局部变量和全局变量;定义装饰器时,也使用了自由变量。这些不同的变量是如何赋值、初始化、查找和修改的?各自的功能细则又是怎样的呢?本文试图回答这个问题。Python中的变量名可以指代变量、函数、类、对象等。一般来说,每个对象都有一个变量名指向,更准确地说是一个绑定。作用域的必要性为什么变量有作用域?我们在Python中遇到的内置变量、局部变量、全局变量和自由变量,都是变量的作用域。该语言区分作用域以重用变量名。作用域的引入,相当于把变量划分到自己的“隔离区”。在不同的“隔离区”,变数变得容易发现。正是因为作用域,我们才可以在函数内部自由使用变量名,而不用担心与全局变量和其他函数中的变量冲突——因为这两个作用域是分开的。BASIC语言只有全局变量,你能想象吗?您在函数中命名的循环变量i很可能与全局变量冲突。写程序很难。并且会造成很多修改和检索问题,维护难度很大。Python变量定义的时间和空间Python有哪些作用域?Python是一种动态类型语言,变量在定义时被赋值。我们可以从以下几个方面来理解这句话的意思:a=1定义赋值时的变量fromtoolsimportcubie定义导入时的变量cubiedeffun():pass定义函数并绑定变量fundeffun(name=None):pass定义变量名为函数fun的形式变量(也是局部变量),同时定义函数,绑定fun类Car:pass定义类,上面绑定类名Car,我们已经明确了变量定义的时刻,下面我们来看一下变量的作用域,即变量的活动空间是如何定义的。变量范围取决于定义它的位置。在函数内部定义的变量和在函数声明中定义的形式参数被视为局部变量。在.py文件中以及在函数和类之外定义的变量被视为全局变量。定义在函数内、嵌套函数外以及被嵌套函数引用的变量都被认为是自由变量。在builtin中定义的变量被认为是内置变量。面对四个变量如此复杂的作用域,用一个例子来说明它们的访问规则。LEGB规则四个范围都遵循LEGB规则,让我们举例说明。importbuiltinsbuiltins.b='builtins'g='global'defouter(o1,o2='o2'):e='enclose'definner(i1,i2='i2'):print(i1,i2,o1,o2,e,g,b)returninnerfun=outer('o1')fun('i1')其输出为i1i2o1o2encloseglobalbuiltins可以看到,输出语句print(i1,i2,o1在嵌套函数inner中外部函数o2,e,g,b)是这个程序的重点。其具体实现如下:打印i1和i2,无疑是局部变量。打印o1和o2,没有局部作用域,往上找外层函数参数。形参也是局部变量,所以这个变量实际上定义在outer函数中,在inner函数之外,inner引用了这个变量,所以算是自由变量。printe,无局部作用域,与上例类似,视为自由变量。printg,没有局部作用域,没有自由变量作用域(闭包),可以追溯到全局作用域找到它。printb,不在局部作用域,不在自由变量作用域(闭包),不在全局作用域,可以追溯到内置变量空间找到它。至此,LEGB规则呼之欲出:在局部空间找不到的变量,应该从上层到上层寻找。这里的LEGB分别指的是Local、Enclose、Global和Builtin。在函数中读取和分配全局变量与在嵌入式函数中读取和分配自由变量之间存在一些差异。nonlocal和global对变量名的赋值和引用是两种不同的情况:赋值:创建变量或修改变量。参考:检索其值。以上两者的区别会导致我们:在函数中给一个全局变量赋值:等于创建了一个局部变量。自由变量:等于创建一个局部变量。参考:正常获取其值。我们将上例中的inner函数修改为如下形式:definener(i1,i2='i2'):e='enclose'g='innerglobal'print(i1,i2,o1,o2,e,g,b)在嵌套函数中,重新定义了g变量,其他语言一般都理解这是对全局变量的重新赋值。但是我们再看最后一条规则:在函数中,给全局变量赋值时,相当于创建了一个局部变量。也就是说此时g已经是一个局部变量——程序末尾的print(g)语句输出的是global而不是修改后的内部global,这也验证了上述规则。完整代码如下:importbuiltinsbuiltins.b='builtins'g='global'defouter(o1,o2='o2'):e='enclose'g='innerglobal'definner(i1,i2='i2'):print(i1,i2,o1,o2,e,g,b)returninnerfun=outer('o1')fun('i1')print(g)输出结果如下:i1i2o1o2encloseinnerglobalbuiltinsglobal不重新赋值,而是使用global变量和自由变量,那就没问题了。自由变量也是如此。为了解决在局部作用域内分配全局变量和自由变量导致的成为局部变量的问题,Python引入了关键字global和nonlocal。definener(i1,i2='i2'):globalgnonlocaleg='innerglobal'e='innerenclose'此时的赋值是对全局变量和自由变量的操作,而不是创建新的局部变量。完整代码如下:importbuiltinsbuiltins.b='builtins'g='global'defouter(o1,o2='o2'):e='enclose'definner(i1,i2='i2'):globalgnonlocaleg='innerglobal'e='innerenclose'print(i1,i2,o1,o2,e,g,b)returninnerfun=outer('o1')fun('i1')print(g)输出结果如下:全局的、免费的和内置的;定义变量的位置决定了变量的范围;范围搜索遵循LEGB规则;为了修改局部范围内的全局变量和自由变量,global关键字和nonlocal关键字。
