在Python中,變量是沒有類型的,這和以往看到的大部分編輯語言都不一樣。在使用變量的時候,不需要提前聲明,只需要給這個變量賦值即可。但是,當用變量的時候,必須要給這個變量賦值;如果只寫一個變量,而沒有賦值,那么Python認為這個變量沒有定義。如下:
1
2
3
4
|
>>> a Traceback (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。可變類型數據對對象操作的時候,不需要再在其他地方申請內存,只需要在此對象后面連續申請(+/-)即可,也就是它的內存地址會保持不變,但區域會變長或者變短。
下面是一些例子:
1
2
3
4
5
6
7
8
|
>>> a = 'xianglong.me' >>> id (a) 140443303134352 >>> a = '1saying.com' >>> id (a) 140443303131776 # 重新賦值之后,變量a的內存地址已經變了 # 'xianglong.me'是str類型,不可變,所以賦值操作知識重新創建了str '1saying.com'對象,然后將變量a指向了它 |
1
2
3
4
5
6
7
8
|
>>> 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指向沒有變 |
二、變量無類型,對象有類型
三、函數值傳遞
先看一個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
def func_int(a): a + = 4 def func_list(a_list): a_list[ 0 ] = 4 t = 0 func_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(),前一個是淺拷貝,后一個是深拷貝。淺拷貝僅僅復制了第一個傳給它的對象,下面的不管了;而深拷貝則將所有能復制的對象都復制了。下面是一個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
a = [[ 1 , 2 , 3 ], [ 4 , 5 , 6 ]] b = a c = copy.copy(a) d = copy.deepcopy(a) a.append( 15 ) a[ 1 ][ 2 ] = 10 print a print b print c print 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定義的本地變量會變成其對應全局變量的一個別名,即是同一個變量。下面的例子可以幫你更好的理解:
1
2
3
4
5
6
7
8
9
10
11
|
a = 44 def test1(): a = 14 print a test1() # 輸出:14 def test2(): global a print a test2() # 輸出:44 |