在Python中,變數是沒有類型的,這和以往看到的大部分編輯語言都不一樣。在使用變數的時候,不需要提前聲明,只需要給這個變數賦值即可。但是,當用變數的時候,必須要給這個變數賦值;如果唯寫一個變數,而沒有賦值,那麼Python認為這個變數沒有定義。如下:
>>> aTraceback (most recent call last): File "<stdin>", line 1, in <module>NameError: name 'a' is not defined
下面我們具體講一下Python中的變數,引用,拷貝和範圍問題。。
一、可變對象 & 不可變對象
在Python中,對象分為兩種:可變對象和不可變對象,不可變對象包括int,float,long,str,tuple等,可變對象包括list,set,dict等。需要注意的是:這裡說的不可變指的是值的不可變。對於不可變類型的變數,如果要更改變數,則會建立一個新值,把變數綁定到新值上,而舊值如果沒有被引用就等待記憶體回收。另外,不可變的類型可以計算hash值,作為字典的key。可變類型資料對對象操作的時候,不需要再在其他地方申請記憶體,只需要在此對象後面連續申請(+/-)即可,也就是它的記憶體位址會保持不變,但地區會變長或者變短。
下面是一些例子:
>>> a = 'xianglong.me'>>> id(a)140443303134352>>> a = '1saying.com'>>> id(a)140443303131776# 重新賦值之後,變數a的記憶體位址已經變了# 'xianglong.me'是str類型,不可變,所以賦值操作知識重新建立了str '1saying.com'對象,然後將變數a指向了它
>>> a_list = [1, 2, 3]>>> id(a_list)140443302951680>>> a_list.append(4)>>> id(a_list)140443302951680# list重新賦值之後,變數a_list的記憶體位址並未改變# [1, 2, 3]是可變的,append操作只是改變了其value,變數a_list指向沒有變
二、變數無類型,對象有類型
三、函數值傳遞
先看一個例子:
def func_int(a): a += 4 def func_list(a_list): a_list[0] = 4 t = 0func_int(t)print t# output: 0 t_list = [1, 2, 3]func_list(t_list)print t_list# output: [4, 2, 3]
對於上面的輸出,不少Python初學者都比較疑惑:第一個例子看起來像是傳值,而第二個例子確實傳引用。其實,解釋這個問題也非常容易,主要是因為可變對象和不可變對象的原因:對於可變對象,對象的操作不會重建對象,而對於不可變對象,每一次操作就重建新的對象。
在函數參數傳遞的時候,Python其實就是把參數裡傳入的變數對應的對象的引用依次賦值給對應的函數內部變數。參照上面的例子來說明更容易理解,func_int中的局部變數"a"其實是全部變數"t"所指向對象的另一個引用,由於整數對象是不可變的,所以當func_int對變數"a"進行修改的時候,實際上是將局部變數"a"指向到了整數對象"1"。所以很明顯,func_list修改的是一個可變的對象,局部變數"a"和全域變數"t_list"指向的還是同一個對象。
四、淺拷貝 & 深拷貝
接下來的問題是:如果我們一定要複製一個可變對象的副本怎麼辦?簡單的賦值已經證明是不可行的,所以Python提供了copy模組,專門用於複製可變對象。copy中有兩個方法:copy()和deepcopy(),前一個是淺拷貝,後一個是深拷貝。淺拷貝僅僅複製了第一個傳給它的對象,下面的不管了;而深拷貝則將所有能複製的對象都複製了。下面是一個例子:
a = [[1, 2, 3], [4, 5, 6]]b = ac = copy.copy(a)d = copy.deepcopy(a) a.append(15)a[1][2] = 10 print aprint bprint cprint d # [[1, 2, 3], [4, 5, 10], 15]# [[1, 2, 3], [4, 5, 10], 15]# [[1, 2, 3], [4, 5, 10]]# [[1, 2, 3], [4, 5, 6]]
五、範圍
在Python程式中建立、改變或尋找變數名時,都是在一個儲存變數名的地方進行中,那個地方我們稱之為命名空間。範圍這個術語也稱之為命名空間。具體地說,在代碼中變數名被賦值(Python中變數聲明即賦值,global 聲明的只是變數的使用域)的位置決定了該變數能被訪問的範圍。函數定義了本地範圍,而模組定義的是全域範圍。
每一個模組都是全域範圍。也就是說,建立於模組檔案頂層的變數具有全域範圍,對於外部存取就成了一個模組對象的屬性。全域範圍的作用範圍僅限於單個檔案。“全域”指的是在一個檔案的頂層變數名對於這個檔案而言是全域的。每次對函數的調用都建立了一個新的本地範圍。Python中也有遞迴,即可以調用自身,每次調用都會建立五個新的本地命名空間。賦值的變數名除非聲明為全域變數,否則均為本地變數。如果需要在函數內部對模組檔案頂層的變數名賦值,需要在函數內部通過 global 語句聲明該變數。所有的變數可歸納為本地、全域或者內建三種。範圍分別為def內部,在一個模組的命名空間內部和預定義的 __builtin__ 模組提供的變數。
變數名引用分為三個範圍進行尋找:首先是本地,然後是函數內(如果有的話),之後是全域,最後是內建。在預設情況下,變數名賦值會建立或者改變本地變數。全域聲明將會給映射到模組檔案內部的範圍的變數名賦值。Python 的變數名解析機制也稱為 LEGB 法則,具體如下:
當在函數中使用未確定的變數名時,Python搜尋4個範圍:本地範圍(L),之後是上一層嵌套結構中 def 或 lambda 的本地範圍(E),之後是全域範圍(G),最後是內建範圍(B)。按這個尋找原則,在第一處找到的地方停止。如果沒有找到,Python 會報錯的。下圖說明了搜尋流程(由內及外):
上面說了,Python中的變數是沒有類型的,但Python其實是區分類型的:Python的所有變數其實都是指向記憶體中的對象的一個指標,都是值的引用,而其類型是跟著對象走的。總結來說:在Python中,類型是屬於對象的,而不是變數, 變數和對象是分離的,對象是記憶體中儲存資料的實體,變數則是指向對象的指標。在《Learning Python》一書中有一個觀點:變數無類型,對象有類型,大概也是說的這個意思。下面是一張說明變數的圖:
Python像PHP一樣提供了一個global文法,global定義的本地變數會變成其對應全域變數的一個別名,即是同一個變數。下面的例子可以幫你更好的理解:
a = 44 def test1(): a = 14 print atest1() # 輸出:14 def test2(): global a print atest2() # 輸出:44