先上個示例:
>>> val = [1]
>>> val[0] = val
>>> val
[[...]]
上述代碼使val中包含自身,而產生了無限遞歸。上述示例表明Python中的變量名為引用類型,賦值只是使得左值指向與右值相同的內存對象。
is
運算符可以判斷兩個引用是否指向了同一個對象,而==
運算符判斷兩個引用指向的值是否相等而不關心指向什麼對象。
對引用不了解的朋友,可以把Python引用與C/C++中的void *
類比,不過因為垃圾回收機制Python引用無需擔心內存洩漏的問題。
上面的示例表明賦值無法為對象建立副本,python中的copy模塊提供了copy
和deepccopy
建立副本。
示例:
>>> import copy
>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = a
>>> c = copy.copy(a)
>>> d = copy.deepcopy(a)
>>> a.append(7)
>>> a[1][2] = 0
>>> a
[[1, 2, 3], [4, 5, 0], 7]
>>> b
[[1, 2, 3], [4, 5, 0], 7]
>>> c
[[1, 2, 3], [4, 5, 0]]
>>> d
[[1, 2, 3], [4, 5, 6]]
淺復制copy.copy只復制父引用指向的對象,其子引用仍指向原來的內存對象,而深復制copy.deepcopy
則會復制所有引用指向的對象。deepcopy 本質上是遞歸 copy。
示例中的副本c,d父對象是a的副本所以a.append
方法對它們沒有影響。
但是copy.copy
創建的副本c中的元素仍指向與a相同的內存對象,而deepcopy
創建的d則指向了自己的元素。
tuple和frozenset之類的容器只是保證其中引用指向不變,但是引用指向的內存對象仍然是可變的。容器的切片對象的機制為淺復制。
x = x + y
,必須創建新的臨時變量然後進行淺復制,性能較差。
x += y
,無需新建臨時對象,只在內存塊末尾增加元素,性能較好。
Python中的參數傳遞采用淺復制的值傳遞。
示例:
>>> def swap(a,b):
... b,a=a,b
...
>>> a = 1
>>> b = 2
>>> swap(1,2)
>>> a
1
>>> b
2
上述示例證明,Python參數傳遞是采用值傳遞的方式。
示例2:
>>> def fun(a):
... a[0] = 2
...
>>> a = [1]
>>> fun(a)
>>> a
[2]
這個示例則證明采用淺復制的方法進行傳遞。
Python中的垃圾回收是以引用計數為主,標記-清除和分代收集為輔。
一組對象互相引用的情況稱為循環引用(交叉引用),若出現這種情況引用計數將無法正確的回收垃圾。,可以包含其他對象引用的容器對象(如list, dict, set,甚至class)都可能產生循環引用。
“標記-清除”法是為了解決循環引用問題。
垃圾標記時,先將集合中對象的引用計數復制一份副本(以免在操作過程中破壞真實的引用計數值),然後操作這個副本,遍歷對象集合,將被引用對象的引用計數副本值減1。
根據引用計數副本值是否為0將集合內的對象分成兩類,reachable和unreachable,其中unreachable是可以被回收的對象。
分代回收的整體思想是:將系統中的所有內存塊根據其存活時間劃分為不同的集合,每個集合就成為一個“代”,垃圾收集頻率隨著“代”的存活時間的增大而減小,存活時間通常利用經過幾次垃圾回收來度量。
弱引用是避免循環引用的一種方法,弱引用不記錄引用計數。當一個對象只有弱引用時可能被垃圾回收器回收。
weakref.ref(obj,[callable])
用於建立一個指向obj的弱引用,當對象被回收前callable可選參數指定的函數將被執行以進行清理工作。
Python引用的那些坑
使用GC分析Python垃圾回收機制
CPython源代碼分析垃圾回收機制