Python之探究類的複製和垃圾回收

一、引用計數和垃圾回收

先看一段代碼:

In [43]: a = 34

In [44]: b = a

In [45]: del a

In [46]: b
Out[46]: 34

In [47]: a
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/home/kehr/<ipython-input-47-60b725f10c9c> in <module>()
----> 1 a

NameError: name 'a' is not defined

就以上這段代碼,說說我的理解。

由於,a和b是指向同一塊內存的引用,那麼,修改a的值b的值一定會改變。

亮點在 “del a” 這一句,執行這句代碼,銷燬的是a這個引用變量呢,還是a指向的那塊內存呢?或者是二者都被銷燬了呢?

那就打印一下b和a

可以看見b和它指向的那塊內存並沒有消失,而b又是和a指向同一塊內存。所以 del a 只是銷燬了a這個引用變量

打印一下a,就會出現 NameError 的錯誤提示。

小結一下: del 只會銷燬內存的引用

那麼還有點問題,是不是由於數字和字符串這些是共享常量纔會出現這種結果呢?我們再來測試一下一個全新的對象試一試

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

In [50]: b = a

In [51]: del a

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

In [53]: a
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/home/kehr/<ipython-input-53-60b725f10c9c> in <module>()
----> 1 a

NameError: name 'a' is not defined

可以看到,結果是一樣的。那麼我是不是可以下一個結論:del 語句只會銷燬對象的引用變量,而對對象沒有影響。

這裏的對象指的是內存中的一塊空間。

那麼,如果 a = [“zhangsan”,"lisi"] 我們銷燬了a,對象["zhangsan","lisi"]去哪裏了呢?會不會內存泄露呢?

其實我們不必擔心,python的垃圾回收機制,會幫我們解決這一問題。這裏我們就需要了解一下對象的引用計數了

每一個對象被創建後,它的引用計數就會加一,當有其它變量再次引用該對象的時候,引用計數再次加一。如果刪除這個對象的一個引用,它的引用計數就會減一。當一個對象的引用計數變爲零的時候,就會被當做垃圾,被處理掉。

二、引用與複製

先來看這幾條條語句:

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

In [3]: b = a

In [4]: import copy

In [5]: c = list(a)

In [6]: d = copy.deepcopy(a)

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

In [8]: c
Out[8]: [1, 2, 3]

In [9]: d
Out[9]: [1, 2, 3]

看起來 b,c,d 都成功的把a的值“複製”過來了,但是他們是不同的。不同在哪裏呢?我們知道,複製,是把一個變量的值copy一份到另外一個變量中,兩個變量相互獨立,對其中一個變量值的修改並不影響另一個變量的值。以上這段代碼的做法體現的不同之處就在這裏。

下面這幾段代碼,先看看效果:

In [7]: a
Out[7]: [1, 2, 3]

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

In [9]: c
Out[9]: [1, 2, 3]

In [10]: d
Out[10]: [1, 2, 3]
b = a, c = list(a), d = copy.deepcopy(a)


In [14]: a
Out[14]: [1, 2, 3]

In [15]: a.append([45,34])

In [16]: a
Out[16]: [1, 2, 3, [45, 34]]

In [17]: b
Out[17]: [1, 2, 3, [45, 34]]

In [18]: c
Out[18]: [1, 2, 3]

In [19]: d
Out[19]: [1, 2, 3]
可以看到a改變之後,只有b變了,c和d是不是真的完成了複製呢?


In [32]: a
Out[32]: [1, 2, 3, [45, 34]]

In [33]: c = list(a)

In [34]: d = copy.deepcopy(a)

In [35]: b
Out[35]: [1, 2, 3, [45, 34]]

In [36]: c
Out[36]: [1, 2, 3, [45, 34]]

In [37]: a[3][0] = 'zhangsan'

In [38]: a
Out[38]: [1, 2, 3, ['zhangsan', 34]]

In [39]: b
Out[39]: [1, 2, 3, ['zhangsan', 34]]

In [40]: c
Out[40]: [1, 2, 3, ['zhangsan', 34]]

In [41]: d
Out[41]: [1, 2, 3, [45, 34]]
到這裏,我們能看出,當a改變,b,c也隨之改變了,d依舊沒變,那是不是說明只有d是成功的完成了複製,b和c都沒有複製成功呢?這隻對了一半,下面我們來一一說明。

第一句:b = a

可能用慣了C和Java對與複製這個概念,認爲這麼做就完成了複製。但是在python中這麼做是不對的。我在python的書上看到這句話的解釋是:“b是對a的引用”

對此,我有兩種理解:

1、有一塊內存地址存放着對象 [1,2,3] ,變量a作爲這塊內存的引用,存儲了它的地址,變量b作爲a的引用存儲了a的地址

2。有一塊內存地址存放着對象 [1,2,3] ,變量a和b作爲這塊內存的引用,都存儲了它的地址。

比較這兩個,我更傾向於第二種理解。

如此,修改a的值b的值也會被修改。


第二句:c = list(a)

這一句實際上是完成了對a的複製的,但是複製的不夠徹底,這種複製被稱爲“淺複製”。爲什麼呢?

這樣看a的內容了。如果a中沒有包含對象(a = [1,2,3]   c = list(a)  ),那麼c的這種複製是成功的,c的內容和a是各自獨立的。複製的時候又創建了一塊新的內存,存放a指向的那塊內存的內容。

但是,如果a中包含對象(a = [1,2,3,[45,34]], c = list(a)),那麼c的這種複製只成功了一半。首先,還是會創建一塊內存,存放a對象中的內容,可是a對象中的內嵌對象是不會被複制過來的,他會由a和c共享。也就是說,除了內嵌脆響之外,其他的元素都是相互獨立的,只有內嵌對象是被共享的。修改內嵌對象的值c和a的值都會改變,修改其它非對象元素,c和a的值只會改變一個。


第三句:d  = copy.deepcopy(a)

這是爲了解決第二句的漏洞而設計的。使用這一句才能夠把a中的所有元素和對象完全複製。d和a是獨立的,修改a中的元素和對象的值都不會影響d中的元素和對象的值。這種複製稱爲“深複製”


這三種複製,不能說哪一種更好,靈活的運用在需要他們的地方,才能體現出各自的價值和優越性。


初學python,本文內容只作爲學習過程的一個總結和理解的記錄。其中不乏有用詞不當的地方。有些地方也是自己實驗過後得出的總結,可能還不夠全面。如果您覺得我的理解有錯誤和偏差,希望您能夠在留言中指出來,我會繼續去探究印證。謝謝啦!

====================更新===============2013.9.24

1、python支持多繼承


對於多繼承中的缺點python是這麼解決的:


設SubDemo繼承了Demo1 和 Demo2,Demo1和Demo2中都擁有函數fun()但是內部實現不同。僞代碼如下:

class Demo1():
.......
def fun(self):
print "Demo1's fun()"
.......

class Demo2():
.......
def fun(self):
print "Demo2's fun()"
....... 

SubDemo聲明如下:
class SubDemo(Demo1,Demo2):
pass


當創建一個SubDemo的實例後調用fun方法,返回的結果是:Demo1's fun()

調換Demo1和Demo2位置再次重複以上步驟,返回的結果是:Demo2's fun()


以上實驗說明,python會根據繼承的父類在參數列表中的位置來決定調用哪一個父類中的重複函數。


2、python中所有對付類的操作都需要顯示的執行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章