首頁 > 軟體

Python中令人頭疼的變數作用域問題,終於弄清楚了

2021-05-21 13:00:27

來源:Python資料之道

作者:大奎

整理:陽哥

大家好,我是陽哥。

學習Python變數過程中,曾經為變數混亂的作用域問題頭疼不已,全局變數、局部變數、自由變數傻傻分不清,今天來跟大家分享 Python變數作用域 的知識點,文章內容由公眾號讀者 大奎 創作。

我們經常聽說Python函數訪問局部變數、全局變數;在定義裝飾器的時候,還會使用自由變數。這些不同的變數是如何賦值、初始化、查詢及修改的呢?各自的作用細則又是什麼樣的呢?本篇嘗試解答這個問題。

Python中的變數名可以指代變數、函數、類、物件等。一般來說,每個物件都有一個變數名指向,更準確說是 繫結

作用域的必要性

為啥變數要有作用域呢?

我們在Python裡遇到的內建、局部、全局及自由變數,就是說變數的作用域。

語言區分作用域,是為了複用變數名。引入作用域,相當於給變數劃分了各自的「隔離區」,在不同」隔離區「裡,查詢變數變得很容易。

正是因為有了作用域,我們在函數內才可以隨意使用變數名,而不擔心其與全局變數、其他函數中的變數衝突——因為這兩個作用域是分割的。

BASIC語言只有全局變數,你能想象嗎?你在一個函數裡命名的迴圈變數i,很可能跟全局變數衝突。寫起程式來,舉步維艱。且會導致很多修改、檢索問題,維護很困難。

Python變數定義的時間和空間

Python 有哪些作用域呢?

Python是動態類型語言,變數是在定義的時候賦值的。這句話的意思我們分以下幾個方面來理解:

a = 1 賦值時定義變數from tools import cubie 匯入時定義變數 cubiedef fun():pass 定義函數,繫結變數fundef fun(name=None):pass 定義變數name為函數fun的形式變數(也是局部變數),同時定義函數,繫結便令funclass Car:pass 定義類,繫結類名Car以上,我們弄清了變數定義的時刻,下面來看變數的作用域,也就是變數的活動空間怎麼規定出來的。

變數作用域取決於其 定義位置

定義在函數內部的變數、定義在函數聲明中的形式參數,視為局部變數。定義在 .py 檔案內的,且函數、類之外的變數,視為全局變數。定義在函數中,巢狀函數外,且被巢狀函數引用的變數,視為自由變數。定義在builtin中的變數,視為內建變數。面對如此複雜的四種變數作用域,用一個例子來說明它們的訪問規則。

LEGB規則

四個作用域遵循LEGB規則,讓我們用一個例子來說明。

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) returninnerfun = outer('o1')fun('i1')

其輸出為 i1 i2 o1 o2 enclose global builtins

可見,在outer函數的巢狀函數inner中的輸出語句 print(i1,i2,o1,o2,e,g,b) 是本程式的重點。其具體執行情況如下:

print i1和i2,毫無疑問的局部變數。print o1和o2,本地作用域沒有,向上查詢到outer函數形參。形參也為局部變數,所以該變數實際定義在outer函數內,inner這個內嵌函數外,而inner內部引用了這個變數,所以視為自由變數。print e,本地作用域沒有,類似上例,視為自由變數。print g,本地作用域沒有,自由變數作用域(閉包)沒有,一直上溯到全局作用局找到。print b,本地作用域沒有,自由變數作用域(閉包)沒有,全局作用局沒有,一致上溯到內建變數空間找到。至此,LEGB規則呼之欲出:在本地空間尋找不到的變數,逐級向上級尋找。這裡的LEGB分別指代Local,Enclose,Global和Builtin。

在函數中讀取賦值全局變數,在內嵌函數中讀取賦值自由變數,會有一些不同的地方。

nonlocal 和 global

對變數名的賦值和引用,是兩種不同的情況:

賦值:創建一個變數或者修改。引用:檢索其值。以上兩者的差別,會導致我們在函數中:

賦值一個全局變數:等於創建一個局部變數。自由變數:等於創建一個局部變數。引用:正常檢索其值。我們修改上例中的inner函數為如下形式:

definner(i1,i2='i2'): e = 'enclose' g = 'inner global' print(i1,i2,o1,o2,e,g,b)

在巢狀函數內,重新定義了g變數,其他語言一般理解這是重新賦值全局變數。但是我們看上條規則:在函數中,賦值一個全局變數時,等於創建一個局部變數。就是說此時的g已經是局部變量了——在程式最後的 print(g) 語句輸出 global,而不是修改後的 inner global 也驗證了以上規則。

完整程式碼如下:

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) returninnerfun = outer('o1')fun('i1')print(g)

輸出結果如下:

i1 i2 o1 o2 enclose inner global builtinsglobal

不重新賦值,只是使用全局變數和自由變數,則沒有問題。

自由變數也是類似的情況。

為了解決局部作用域中賦值全局變數和自由變數導致的變成局部變數問題,Python引入關鍵字 global 和 nonlocal 。

definner(i1,i2='i2'):global g nonlocal e g = 'inner global' e = 'inner enclose'

此時的賦值,則分別是對全局變數和自由變數的操作,而非新建局部變數。

完整程式碼如下:

importbuiltinsbuiltins.b = 'builtins'g = 'global'defouter(o1,o2='o2'): e = 'enclose'definner(i1,i2='i2'):global g nonlocal e g = 'inner global' e = 'inner enclose' print(i1,i2,o1,o2,e,g,b) return inner fun = outer('o1') fun('i1')print(g)

輸出結果如下:

i1 i2 o1 o2 inner enclose inner global builtinsinner global

總結

Python的作用域分為四種,分別是局部、全局、自由和內建;定義變數的位置決定了變數的作用域;作用域的查詢遵守LEGB規則;為了在局部作用域中修改全局變數和自由變數,引入了 global 關鍵字和 nonlocal 關鍵字。


IT145.com E-mail:sddin#qq.com