1. 程式人生 > >Python中的賦值、引用和深淺拷貝

Python中的賦值、引用和深淺拷貝

全域性變數

在函式之外建立的變數屬於__main__,又被稱為全域性變數。它們可以在__main__中的任意函式中訪問,與區域性變數在函式結束時消失不同,全域性變數可以在不同函式的呼叫之間持久存在。全域性變數常常用作標誌(Flags)。標誌是一種布林型變數,可以標誌一個條件是否為真。

verbose = True

def example():
    if verbose:
        print('你好,今天天氣很好!')
    else:
        print('你好')

如果在函式裡嘗試給全域性變數賦值,必須先用global關鍵字進行宣告,否則函式會建立一個同名區域性變數而不是使用全域性變數。

verbose = True

def example():
    global verbose
    verbose = False
    print('你好')

物件、值和別名

在Python中,string、tuple和number是不可變物件,而list、dict等是可變物件。

先來看一段程式碼:

b = [1, 2, 3]
a = b
print(a is b) # True
b.append(4)
print(a) # [1, 2, 3, 4]

a is b返回True,說明這python內部a與b是相同的,變數a與變數b都指向同一個物件。此時稱a和b為這個物件的別名。當物件的值發生改變時,a和b自然也會隨之改變。如果a、b只是值相等而不指向同一個物件,我們稱a與b是相等的。相同必定相等,相等不一定相同。

a = [1, 2, 3]
b = [1, 2, 3]
print(a is b) # False

我們平時說的【變數】其實只是標籤,是對記憶體中物件的引用。賦值操作只是給變數一個指向物件的引用

is與==的區別

寫程式碼的時候常常用is和==來比較兩個物件是否相等,但是它們有什麼不同呢?參考下面的例子:

a = 1
b = 1
a == b # True
a is b # True

a = 888
b = 888
a == b # True
a is b # False

a = 'hello'
b = 'hello'
a is b # True
a == b # True

a = 'hello world'
b = 'hello world'
a == b # True
a is b # False

is和==的結果不同!不是說好的都是比較兩個物件是否相等嗎?怎麼到這裡變了樣了?不急,先介紹一下python內建的一個函式:id(),這個函式會列印引數的記憶體地址,讓我們來試試:

a = 888
b = 888
id(a) # 1939743592336
id(b) # 1939745557808

a = 'hello world'
b = 'hello world'
id(a) # 1939745897200
id(b) # 1939745912624

可以看到,儘管a、b的值是相同的,但是其記憶體地址卻不同。那麼答案就很顯然了,is比較的是兩個物件的記憶體地址是否相等,比較的是兩個物件的值是否相等。這樣就能解釋為什麼is和的結果不同了。

But wait,那麼為什麼當a、b的值為1和’hello’時,is與==的結果是一樣的呢?這就要說到python的小整數池和垃圾回收機制了。python為了讓執行速度快些,在記憶體中專門開闢了一塊區域放置-5到256,所有代表-5到256的物件都會指向這個區域。

類似的,字串型別作為Python中最常用的資料型別之一,Python直譯器為了提高字串使用的效率和使用效能,做了很多優化。

例如:Python直譯器中使用了intern(字串駐留)的技術來提高字串效率,什麼是intern機制?即值同樣的字串物件僅僅會儲存一份,放在一個字串儲蓄池中,是共用的,當然,肯定不能改變,這也決定了字串必須是不可變物件。

同時,如果字串中有空格,預設不啟用intern機制。對字串儲蓄池中的字串使用is和==比較會得到相同的結果。:

a = 1
b = 1
id(a) # 1963327952
id(b) # 1963327952

a = 'hello' 
b = 'hello' 
id(a) # 1939745887600
id(b) # 1939745887600

注意:在shell中,僅有以下劃線、數字、字母組成的字串會被intern。而pycharm中只要是同一個字串不超過20個字元都被加入到池中

python傳參

python函式引數傳遞是引用傳遞:

def test(n):
    print(id(n))
k = "string"
id(k) # 2305161642256
test(k) # 2305161642256

深淺拷貝

常用的拷貝方式有:

  • 沒有限制條件的分片表示式(L[:])能夠複製序列,但此法只能淺層複製。
  • 字典 copy 方法,D.copy() 能夠複製字典,但此法只能淺層複製
  • 有些內建函式,例如 list,能夠淺拷貝 list(L)
  • copy 標準庫模組能夠生成完整拷貝:deepcopy 本質上是遞迴 copy,是深層複製

淺拷貝

淺拷貝屬於“新瓶裝舊酒”,即生成了一個新的變數,而變數所指向的物件和原來的是一樣的:

l = ["hello", [2, 3, 4]]
id(l) # 3048239386824
[id(i) for i in l] # [1524761040, 1524761072]

k = l.copy()
id(k) # 3048239387080,地址不同,k是另一個變數
[id(i) for i in k] # [1524761040, 1524761072],地址相同,指向同一個變數

深拷貝

深拷貝屬於“新瓶裝新酒”,即生成了一個新變數,指向和原物件相等的新物件(不可變物件除外):

import copy

l = ["hello world", [2, 3, 4]]
id(l) # 3048239386824
[id(i) for i in l] # [3048239385040, 3048239387080]

k = copy.deepcopy(l)
id(k) # 3048240927048,地址不同,k是另一個變數
[id(i) for i in k]  # [3048239385040, 3048240927304],字串是不可變物件,所以仍指向原地址,對於list