W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
你的程序創(chuàng)建了很多循環(huán)引用數(shù)據(jù)結(jié)構(gòu)(比如樹(shù)、圖、觀察者模式等),你碰到了內(nèi)存管理難題。
一個(gè)簡(jiǎn)單的循環(huán)引用數(shù)據(jù)結(jié)構(gòu)例子就是一個(gè)樹(shù)形結(jié)構(gòu),雙親節(jié)點(diǎn)有指針指向孩子節(jié)點(diǎn),孩子節(jié)點(diǎn)又返回來(lái)指向雙親節(jié)點(diǎn)。這種情況下,可以考慮使用 weakref
庫(kù)中的弱引用。例如:
import weakref
class Node:
def __init__(self, value):
self.value = value
self._parent = None
self.children = []
def __repr__(self):
return 'Node({!r:})'.format(self.value)
# property that manages the parent as a weak-reference
@property
def parent(self):
return None if self._parent is None else self._parent()
@parent.setter
def parent(self, node):
self._parent = weakref.ref(node)
def add_child(self, child):
self.children.append(child)
child.parent = self
這種是想方式允許parent靜默終止。例如:
>>> root = Node('parent')
>>> c1 = Node('child')
>>> root.add_child(c1)
>>> print(c1.parent)
Node('parent')
>>> del root
>>> print(c1.parent)
None
>>>
循環(huán)引用的數(shù)據(jù)結(jié)構(gòu)在Python中是一個(gè)很棘手的問(wèn)題,因?yàn)檎5睦厥諜C(jī)制不能適用于這種情形。例如考慮如下代碼:
# Class just to illustrate when deletion occurs
class Data:
def __del__(self):
print('Data.__del__')
# Node class involving a cycle
class Node:
def __init__(self):
self.data = Data()
self.parent = None
self.children = []
def add_child(self, child):
self.children.append(child)
child.parent = self
下面我們使用這個(gè)代碼來(lái)做一些垃圾回收試驗(yàn):
>>> a = Data()
>>> del a # Immediately deleted
Data.__del__
>>> a = Node()
>>> del a # Immediately deleted
Data.__del__
>>> a = Node()
>>> a.add_child(Node())
>>> del a # Not deleted (no message)
>>>
可以看到,最后一個(gè)的刪除時(shí)打印語(yǔ)句沒(méi)有出現(xiàn)。原因是Python的垃圾回收機(jī)制是基于簡(jiǎn)單的引用計(jì)數(shù)。當(dāng)一個(gè)對(duì)象的引用數(shù)變成0的時(shí)候才會(huì)立即刪除掉。而對(duì)于循環(huán)引用這個(gè)條件永遠(yuǎn)不會(huì)成立。因此,在上面例子中最后部分,父節(jié)點(diǎn)和孩子節(jié)點(diǎn)互相擁有對(duì)方的引用,導(dǎo)致每個(gè)對(duì)象的引用計(jì)數(shù)都不可能變成0。
Python有另外的垃圾回收器來(lái)專門針對(duì)循環(huán)引用的,但是你永遠(yuǎn)不知道它什么時(shí)候會(huì)觸發(fā)。另外你還可以手動(dòng)的觸發(fā)它,但是代碼看上去很挫:
>>> import gc
>>> gc.collect() # Force collection
Data.__del__
Data.__del__
>>>
如果循環(huán)引用的對(duì)象自己還定義了自己的 __del__()
方法,那么會(huì)讓情況變得更糟糕。假設(shè)你像下面這樣給Node定義自己的 __del__()
方法:
# Node class involving a cycle
class Node:
def __init__(self):
self.data = Data()
self.parent = None
self.children = []
def add_child(self, child):
self.children.append(child)
child.parent = self
# NEVER DEFINE LIKE THIS.
# Only here to illustrate pathological behavior
def __del__(self):
del self.data
del.parent
del.children
這種情況下,垃圾回收永遠(yuǎn)都不會(huì)去回收這個(gè)對(duì)象的,還會(huì)導(dǎo)致內(nèi)存泄露。如果你試著去運(yùn)行它會(huì)發(fā)現(xiàn),Data.__del__
消息永遠(yuǎn)不會(huì)出現(xiàn)了,甚至在你強(qiáng)制內(nèi)存回收時(shí):
>>> a = Node()
>>> a.add_child(Node()
>>> del a # No message (not collected)
>>> import gc
>>> gc.collect() # No message (not collected)
>>>
弱引用消除了引用循環(huán)的這個(gè)問(wèn)題,本質(zhì)來(lái)講,弱引用就是一個(gè)對(duì)象指針,它不會(huì)增加它的引用計(jì)數(shù)。你可以通過(guò) weakref
來(lái)創(chuàng)建弱引用。例如:
>>> import weakref
>>> a = Node()
>>> a_ref = weakref.ref(a)
>>> a_ref
<weakref at 0x100581f70; to 'Node' at 0x1005c5410>
>>>
為了訪問(wèn)弱引用所引用的對(duì)象,你可以像函數(shù)一樣去調(diào)用它即可。如果那個(gè)對(duì)象還存在就會(huì)返回它,否則就返回一個(gè)None。由于原始對(duì)象的引用計(jì)數(shù)沒(méi)有增加,那么就可以去刪除它了。例如;
>>> print(a_ref())
<__main__.Node object at 0x1005c5410>
>>> del a
Data.__del__
>>> print(a_ref())
None
>>>
通過(guò)這里演示的弱引用技術(shù),你會(huì)發(fā)現(xiàn)不再有循環(huán)引用問(wèn)題了,一旦某個(gè)節(jié)點(diǎn)不被使用了,垃圾回收器立即回收它。你還能參考8.25小節(jié)關(guān)于弱引用的另外一個(gè)例子。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: