1. 程式人生 > >Python可變物件和不可變物件

Python可變物件和不可變物件

Python中一切皆物件,每個物件都有其唯一的id,對應的型別和值,其中id指的是物件在記憶體中的位置。根據物件的值是否可修改分為可變物件和不可變物件。其中, 不可物件包括:數字,字串,tuple 可變物件包括:list,dict,set Python中的變數可以指向任意物件,可以將變數都看成是指標,儲存了所指向物件的記憶體地址(物件的引用)。 ### 不可變物件 對於不可變物件,如果要更新變數引用的不可變物件的值,會建立新的物件,改變物件的引用,舉個例子: ```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時會進行垃圾回收。 所以,不可變物件的優點是對於相同的物件,無論多少個引用,在記憶體中只佔用一個地址,缺點是更新需要建立新的物件,因此效率不高。 ### 可變物件 對於可變物件,舉個例子: ```python 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, +=這種操作,而不包括新的賦值操作,賦值操作是會新建一個物件的。比如: ```python In [96]: a = [1, 2, 3] In [97]: b = a In [98]: a = [1] In [99]: b Out[99]: [1, 2, 3] ``` ### 引數傳遞問題 因為可變物件和不可變物件的特性,因此在引數傳遞上需要注意,詳情可參考 [我的回答](https://www.zhihu.com/question/20591688/answer/128044544) ### 深拷貝和淺拷貝 首先,舉個例子: ```python 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方法。 所以,上述程式碼可按需更新為: ```python 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 ```