Python中变量作用域问题
我們經(jīng)常聽(tīng)說(shuō)Python函數(shù)訪(fǎng)問(wèn)局部變量、全局變量;在定義裝飾器的時(shí)候,還會(huì)使用自由變量。這些不同的變量是如何賦值、初始化、查找及修改的呢?各自的作用細(xì)則又是什么樣的呢?本篇嘗試解答這個(gè)問(wèn)題。
Python中的變量名可以指代變量、函數(shù)、類(lèi)、對(duì)象等。一般來(lái)說(shuō),每個(gè)對(duì)象都有一個(gè)變量名指向,更準(zhǔn)確說(shuō)是?綁定。
作用域的必要性
為啥變量要有作用域呢?
我們?cè)赑ython里遇到的內(nèi)置、局部、全局及自由變量,就是說(shuō)變量的作用域。
語(yǔ)言區(qū)分作用域,是為了復(fù)用變量名。引入作用域,相當(dāng)于給變量劃分了各自的“隔離區(qū)”,在不同”隔離區(qū)“里,查找變量變得很容易。
正是因?yàn)橛辛俗饔糜?#xff0c;我們?cè)诤瘮?shù)內(nèi)才可以隨意使用變量名,而不擔(dān)心其與全局變量、其他函數(shù)中的變量沖突——因?yàn)檫@兩個(gè)作用域是分割的。
BASIC語(yǔ)言只有全局變量,你能想象嗎?你在一個(gè)函數(shù)里命名的循環(huán)變量i,很可能跟全局變量沖突。寫(xiě)起程序來(lái),舉步維艱。且會(huì)導(dǎo)致很多修改、檢索問(wèn)題,維護(hù)很困難。
Python變量定義的時(shí)間和空間
Python 有哪些作用域呢?
Python是動(dòng)態(tài)類(lèi)型語(yǔ)言,變量是在定義的時(shí)候賦值的。這句話(huà)的意思我們分以下幾個(gè)方面來(lái)理解:
-
a = 1?賦值時(shí)定義變量
-
from tools import cubie?導(dǎo)入時(shí)定義變量 cubie
-
def fun():pass?定義函數(shù),綁定變量fun
-
def fun(name=None):pass?定義變量name為函數(shù)fun的形式變量(也是局部變量),同時(shí)定義函數(shù),綁定便令fun
-
class Car:pass??定義類(lèi),綁定類(lèi)名Car
以上,我們弄清了變量定義的時(shí)刻,下面來(lái)看變量的作用域,也就是變量的活動(dòng)空間怎么規(guī)定出來(lái)的。
變量作用域取決于其?定義位置。
-
定義在函數(shù)內(nèi)部的變量、定義在函數(shù)聲明中的形式參數(shù),視為局部變量。
-
定義在?.py?文件內(nèi)的,且函數(shù)、類(lèi)之外的變量,視為全局變量。
-
定義在函數(shù)中,嵌套函數(shù)外,且被嵌套函數(shù)引用的變量,視為自由變量。
-
定義在builtin中的變量,視為內(nèi)置變量。
面對(duì)如此復(fù)雜的四種變量作用域,用一個(gè)例子來(lái)說(shuō)明它們的訪(fǎng)問(wèn)規(guī)則。
LEGB規(guī)則
四個(gè)作用域遵循LEGB規(guī)則,讓我們用一個(gè)例子來(lái)說(shuō)明。
import?builtinsbuiltins.b?=?'builtins' g?=?'global'def?outer(o1,o2='o2'):e?=?'enclose'def?inner(i1,i2='i2'):print(i1,i2,o1,o2,e,g,b)return?inner?fun?=?outer('o1')? fun('i1')其輸出為?i1 i2 o1 o2 enclose global builtins
可見(jiàn),在outer函數(shù)的嵌套函數(shù)inner中的輸出語(yǔ)句print(i1,i2,o1,o2,e,g,b)?是本程序的重點(diǎn)。其具體執(zhí)行情況如下:
-
print i1和i2,毫無(wú)疑問(wèn)的局部變量。
-
print o1和o2,本地作用域沒(méi)有,向上查找到outer函數(shù)形參。形參也為局部變量,所以該變量實(shí)際定義在outer函數(shù)內(nèi),inner這個(gè)內(nèi)嵌函數(shù)外,而inner內(nèi)部引用了這個(gè)變量,所以視為自由變量。
-
print e,本地作用域沒(méi)有,類(lèi)似上例,視為自由變量。
-
print g,本地作用域沒(méi)有,自由變量作用域(閉包)沒(méi)有,一直上溯到全局作用局找到。
-
print b,本地作用域沒(méi)有,自由變量作用域(閉包)沒(méi)有,全局作用局沒(méi)有,一致上溯到內(nèi)置變量空間找到。
至此,LEGB規(guī)則呼之欲出:在本地空間尋找不到的變量,逐級(jí)向上級(jí)尋找。這里的LEGB分別指代Local,Enclose,Global和Builtin。
在函數(shù)中讀取和賦值全局變量,在內(nèi)嵌函數(shù)中讀取和賦值自由變量,會(huì)有一些不同的地方。
nonlocal 和 global
對(duì)變量名的賦值和引用,是兩種不同的情況:
-
賦值:創(chuàng)建一個(gè)變量或者修改。
-
引用:檢索其值。
以上兩者的差別,會(huì)導(dǎo)致我們?cè)诤瘮?shù)中:
-
賦值一個(gè)
-
全局變量:等于創(chuàng)建一個(gè)局部變量。
-
自由變量:等于創(chuàng)建一個(gè)局部變量。
-
-
引用:正常檢索其值。
我們修改上例中的inner函數(shù)為如下形式:
def?inner(i1,i2='i2'):e?=?'enclose'g?=?'inner?global'print(i1,i2,o1,o2,e,g,b)在嵌套函數(shù)內(nèi),重新定義了g變量,其他語(yǔ)言一般理解這是重新賦值全局變量。但是我們看上條規(guī)則:在函數(shù)中,賦值一個(gè)全局變量時(shí),等于創(chuàng)建一個(gè)局部變量。就是說(shuō)此時(shí)的g已經(jīng)是局部變量了——在程序最后的?print(g)?語(yǔ)句輸出?global,而不是修改后的?inner global?也驗(yàn)證了以上規(guī)則。
完整代碼如下:
import?builtinsbuiltins.b?=?'builtins' g?=?'global'def?outer(o1,o2='o2'):e?=?'enclose'g?=?'inner?global'def?inner(i1,i2='i2'):print(i1,i2,o1,o2,e,g,b)return?inner?fun?=?outer('o1')? fun('i1')print(g)輸出結(jié)果如下:
i1?i2?o1?o2?enclose?inner?global?builtins global不重新賦值,只是使用全局變量和自由變量,則沒(méi)有問(wèn)題。
自由變量也是類(lèi)似的情況。
為了解決局部作用域中賦值全局變量和自由變量導(dǎo)致的變成局部變量問(wèn)題,Python引入關(guān)鍵字?global?和?nonlocal?。
def?inner(i1,i2='i2'):global?gnonlocal?eg?=?'inner?global'e?=?'inner?enclose'此時(shí)的賦值,則分別是對(duì)全局變量和自由變量的操作,而非新建局部變量。
完整代碼如下:
import?builtinsbuiltins.b?=?'builtins' g?=?'global'def?outer(o1,o2='o2'):e?=?'enclose'def?inner(i1,i2='i2'):global?gnonlocal?eg?=?'inner?global'e?=?'inner?enclose'print(i1,i2,o1,o2,e,g,b)return?inner?fun?=?outer('o1')? fun('i1')print(g)輸出結(jié)果如下:
i1?i2?o1?o2?inner?enclose?inner?global?builtins inner?global總結(jié)
-
Python的作用域分為四種,分別是局部、全局、自由和內(nèi)置;
-
定義變量的位置決定了變量的作用域;
-
作用域的查找遵守LEGB規(guī)則;
-
為了在局部作用域中修改全局變量和自由變量,引入了?global?關(guān)鍵字和nonlocal?關(guān)鍵字。
總結(jié)
以上是生活随笔為你收集整理的Python中变量作用域问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: MySQL-InnoDB究竟如何巧妙实现
- 下一篇: Python字符串格式化之format方