Python可變對象和不可變對象

Python中一切皆對象,每個對象都有其唯一的id,對應的類型和值,其中id指的是對象在記憶體中的位置。根據對象的值是否可修改分為可變對象和不可變對象。其中,

不可對象包括:數字,字元串,tuple

可變對象包括:list,dict,set

Python中的變數可以指向任意對象,可以將變數都看成是指針,保存了所指向對象的記憶體地址(對象的引用)。

不可變對象

對於不可變對象,如果要更新變數引用的不可變對象的值,會創建新的對象,改變對象的引用,舉個例子:

In [41]: x = 1

In [42]: y = x

In [43]: print(id(x))
140719461487648

In [44]: x = 2

In [45]: print(id(y))
140719461487648

In [46]: print(id(x))
140719461487680

In [47]: print(id(2))
140719461487680

上述是int類型的一個實例,可以看到:

  1. 想要變數的值,會在記憶體中創建一個新的對象,變數指向新的對象。
  2. 對於值為1或者2,不管幾個引用指向它,記憶體中都只佔用了一個地址,在Python內部會通過引用計數來記錄指向該地址的引用個數,當引用個數為0時會進行垃圾回收。

所以,不可變對象的優點是對於相同的對象,無論多少個引用,在記憶體中只佔用一個地址,缺點是更新需要創建新的對象,因此效率不高。

可變對象

對於可變對象,舉個例子:

In [57]: a = [1, 2]

In [58]: b = a

In [59]: print(id(a), id(b))
1961088949320 1961088949320

In [60]: a.append(3)

In [61]: print(a, b)
[1, 2, 3] [1, 2, 3]

In [62]: print(id(a), id(b))
1961088949320 1961088949320

In [63]: a = [1, 2, 3]

In [64]: print(id(a))
1961088989704

可以看到:

  1. 值的變化是在原有對象的基礎上進行更新的,變數引用的地址沒有變化。
  2. 對於一個變數的兩次賦值操作,值相同,但是引用的地址是不同的,也就是同樣值的對象,在記憶體中是保存了多份的,地址是不同的。

注意,我們研究可變對象的變化,研究的是同一對象,也就是可變指的是append, +=這種操作,而不包括新的賦值操作,賦值操作是會新建一個對象的。比如:

In [96]: a = [1, 2, 3]

In [97]: b = a

In [98]: a = [1]

In [99]: b
Out[99]: [1, 2, 3]

參數傳遞問題

因為可變對象和不可變對象的特性,因此在參數傳遞上需要注意,詳情可參考 我的回答

深拷貝和淺拷貝

首先,舉個例子:

In [69]: data = [{'name': 'a', 'deleted': True}, {'name' : 'b', 'deleted': False}, {'name': 'c', 'deleted': False}]

In [70]: print(data)
[{'name': 'a', 'deleted': True}, {'name': 'b', 'deleted': False}, {'name': 'c', 'deleted': False}]

In [71]: def add(data_list):
    ...:     for item in data_list:
    ...:         if item.get('deleted'):
    ...:             data_list.remove(item)
    ...:     return data_list
    ...:

In [72]: add_result = add(data)

In [73]: print(add_result)
[{'name': 'b', 'deleted': False}, {'name': 'c', 'deleted': False}]

In [74]: print(data)
[{'name': 'b', 'deleted': False}, {'name': 'c', 'deleted': False}]

你會發現調用了add方法之後,data已經變了,在之後的程式碼中你已經無法再使用原來的data了,具體的原因在參數傳遞那個問題中我有說明。

但是,當你希望在add方法中並不會修改data的值,要怎麼做呢?

這時候,你需要了解下深拷貝和淺拷貝:

深拷貝和淺拷貝的概念:

  1. 淺拷貝(shallow copy):構造一個新的對象並將原對象中的引用插入到新對象中,只拷貝了對象的地址,而不對對應地址所指向的具體內容進行拷貝,也就是依然使用原對象的引用。實現方式包括:工廠函數(list, set等)、切片,copy模組的copy方法。
  2. 深拷貝(deep copy):複製了對象的和引用,深拷貝得到的對象和原對象是相互獨立的。實現方式:copy模組的deepcopy方法。

所以,上述程式碼可按需更新為:

def add(data_list):
    ret_data_list = deepcopy(data_list)
    for item in ret_data_list:
        if item.get('deleted'):
            ret_data_list.remove(item)
    return ret_data_list

以上。

Tags: