Python中的物件引用、可變性和垃圾回收

Hanwencheng發表於2019-02-16

導語:本文章記錄了本人在學習Python基礎之物件導向篇的重點知識及個人心得,打算入門Python的朋友們可以來一起學習並交流。

本文重點:

1、明確變數儲存的是引用這一本質;
2、熟悉物件引用的基礎知識;
3、掌握深複製和淺複製;
4、熟悉函式傳參引用時潛在的麻煩並避免。

一、物件引用基礎知識

  1. 變數:是標註而不是容器。對引用式變數而言,是把變數分配給物件,反過來理解則不合理。
  2. 別名:同一個物件的不同標註就是別名,別名指向同一物件。
  3. 標識:可以把標識理解為物件在記憶體中的地址。每個變數都有標識、型別和值。物件一旦建立,它的標識絕不會變。is運算子比較兩個物件的標識;id()函式返回物件標識的整數表示,即物件的記憶體地址。
  4. 相等性:用==運算子比較兩個物件的值是否相等。注意a==b是語法糖,等同於a.__eq__(b)。
  5. ==和is的選擇:我們關注值的頻率比標識要高。

當比較變數和單例值的時候應該用is。例如:X is None 或 X is not None。

is運算子比==要快,因為is不能過載。

二、可變性

1、元組的相對不可變性:

指tuple資料結構的物理內容(即儲存的引用)不可變。也就是說元組中不可變的是元素的標識,但元組的值會隨著引用的可變物件變化而變化。
tuple,list,dict,set儲存的是物件的引用,而str,byte,array.array儲存的是物件的值(字元,位元組,數字)。

2、淺複製與深複製

淺複製:當複製tuple,list,dict,set時,副本之間共享內部物件的引用。copy.copy()
深複製:當複製tuple,list,dict,set時,副本之間不共享內部物件的引用。copy.deepcopy()

eg:淺複製小例子

list1=[1,(55,66),[7,8,9]]
list2=list(list1)#構建副本預設為淺複製
list2[1]+=(77,88)#對元組進行+=運算會解綁list2[1],並與右端運算後的值之間重新繫結起來。
list2[2]+=[10]#對列表進行iadd運算會就地修改列表,不會發生重新繫結。
list2[0]*=3
list1[2].pop(0)
print(list1)
print(list2)
#輸出:
[1, (55, 66), [8, 9, 10]]
[3, (55, 66, 77, 88), [8, 9, 10]]

分析:list2是list1的副本,我們對list2的三個元素均作了改動,但只有列表元素的改動影響到了list1。原因在於list1和list2的第三個列表元素共享引用,因此影響也會同步;元組因為發生瞭解綁的運算所以影響未同步到list1;至於數值的影響不同步的原因是因為淺複製針對str,byte,array.array這些物件直接將值重新儲存到副本中來,不存在共享引用的內部邏輯。

深複製注意事項

  • 深複製處在迴圈引用的物件時,深複製演算法會進入無限迴圈中。
  • 一些物件可能會引用不該複製的外部資源或單例值,這些物件的深複製的結果可能太深。

3、函式的引數作為引用時:

Python唯一支援的引數傳遞模式是共享傳參。共享傳參指函式的各個形式引數獲得實參中各個引用的副本,即函式內部的形參是實參的別名。

  • 函式可能會修改作為引數傳入的可變物件。
    (1)這個行為無法避免,除非在本地建立副本,或者使用不可變物件。

    (2)因此在類中直接把引數賦值給例項變數之前一定要三思,因為這樣會為引數物件建立別名,修改傳入引數指向的可變物件。

eg:函式修改作為引數傳入的全域性變數

def f(a, b):
    a += b
    return a
a = [1, 2]
b = [3, 4]
f(a, b)
print(a, b)#輸出[1, 2, 3, 4], [3, 4],此時列表a已經發生變化。
t = (10, 20)
u = (30, 40)
f(t, u)
print(t, u)#輸出((10, 20), (30, 40)),此時元組t沒有發生變化。
  • 使用可變型別作為函式引數的預設值有危險。
    原因在於包含此類函式的類的例項在未指定初始值時會使用同一個可變預設值。當一個例項就地修改引數時會影響其他例項對預設值的呼叫。

三、垃圾回收

1、垃圾回收的判定規則:

  • 主要採用引用計數演算法。
    在Python中每個物件的引用都會有統計。當引用計數歸零時,物件就會立即銷燬。
  • 除了迴圈引用外沒有其他引用,處在迴圈引用的物件都會被銷燬。

2、弱引用

  • 某些情況下可能需要儲存物件的引用,但不留存物件本身,此時可以藉助弱引用實現。弱引用不會妨礙物件被當做垃圾回收。
  • 弱引用是一種低層機制,是weakref模組中WeakValueDictionary、WeakKeyDictionary和WeakSet等有用的集合類,以及finalize函式的底層支援。
  • 弱引用的侷限性
    弱引用所指物件可以是set,使用者自定義的類,list和dict的子類。不可以是int、tuple的例項及子類,也不可以是list例項或dict例項。

四、Python對不可變型別施加的把戲

1、使用一個元組來構造另一個元組,得到的其實是同一個元組。

2、比較字串或整數是否相等時,應該使用==而不是is。這是由於Python直譯器內部駐留的特性所導致的。

相關文章