python weakref 迴圈引用

2022-07-27 09:06:11 字數 3811 閱讀 6019

前面已經熟悉了python記憶體**機制:引用計數,分代**。

但是,仍有乙個問題:無法**迴圈引用物件。

只有容器物件才會形成迴圈引用,比如list、class、deque、dict、set等都屬於容器型別。

import gc  

class a(object):

def __init__(self):

self.data = [x for x in range(100000)]

self.child = none

def __del__(self):

pass

def cycle_ref():

a1 = a()

a2 = a()

a1.child = a2

a2.child = a1

if __name__ == '__main__':

import time

while true:

time.sleep(0.5)

cycle_ref()

測試結果:

理論上a1,a2都是區域性變數,應該被**,但實際是如果使用迴圈引用,程序占用記憶體會不停飆公升;

注釋掉迴圈引用的兩句,程序占用記憶體保持在11.5m。

結果表明,迴圈引用會導致記憶體洩漏。

網上的文件基本上表示gc無法**迴圈引用的物件。

繼續測試

import gc  

class a(object):

def __init__(self):

self.data = [x for x in range(100000)]

self.child = none

def __del__(self):

pass

def cycle_ref():

a1 = a()

a2 = a()

a1.child = a2

a2.child = a1

if __name__ == '__main__':

import time

while true:

time.sleep(0.5)

cycle_ref()

gc.collect()

print(gc.garbage)

測試結果(python 3.7.7, win8, idle):

手動gc.collect()可以**迴圈引用物件,記憶體占用保持在12m左右;

del方法並不會影響**。部分文件聲稱類自定義del方法會導致gc無法**迴圈引用物件,實測結果並不支援這一點;

gc.garbage輸出仍保持空。

解決方法有兩種:手動/自動。

手動很簡單,在不使用時解除引用。

def cycle_ref():  

a1 = a()

a2 = a()

a1.child = a2

a2.child = a1

# 解除迴圈引用,避免記憶體洩露

a1.child = none

a2.child = none

當然,這比較考驗開發的功底,也容易出錯,weakref就是自動化的解決方案。

python標準庫提供了weakref模組,弱引用不會在引用計數中計數,其主要目的是解決迴圈引用。並非所有的物件都支援weakref,例如list和dict就不支援。下面是weakref比較常用的方法:

class weakref.ref(object[, callback]) :建立乙個弱引用物件,object是被引用的物件,callback是**函式(當被引用物件被刪除時,呼叫該**函式);

weakref.proxy(object[, callback]):建立乙個用弱引用實現的**物件,引數同上;

weakref.getweakrefcount(object) :獲取物件object關聯的弱引用物件數;

weakref.getweakrefs(object):獲取object關聯的弱引用物件列表;

class weakref.weakkeydictionary([dict]):建立key為弱引用物件的字典;

class weakref.weakvaluedictionary([dict]):建立value為弱引用物件的字典;

class weakref.weakset([elements]):建立成員為弱引用物件的集合物件。

使用r_obj = weakref.ref(obj)建立的物件引用時需要使用r_obj()形式呼叫,所以常用做法是使用r_obj = weakref.proxy(obj);這樣呼叫時可以直接使用r_obj。

對上面的案例**改造一下便可解決記憶體洩漏問題:

import gc  

import weakref

class a(object):

def __init__(self):

self.data = [x for x in range(100000)]

self.child = none

def __del__(self):

pass

def cycle_ref():

a1 = a()

a2 = a()

a1.child = weakref.proxy(a2)

a2.child = weakref.proxy(a1)

if __name__ == '__main__':

import time

while true:

time.sleep(0.5)

cycle_ref()

#gc.collect()

print(gc.garbage)

下面的**中gc.garbage總是為空,與文件所描述的情形不符。

可以觀察到刪除迴圈引用的兩個物件時不一定會立即**,需要手動呼叫gc.collect(0、1、2)才能**。

但gc.garbage始終為空。

class a():  

def __del__(self):

print('del a')

class b():

def __del__(self):

print('del b')

a = a()

b = b()

a._n = b

b._n = a

import gc

class node(object):

def __init__(self, data):

self.data = data

self.parent = none

self.children =

def add_child(self, child):

child.parent = self

def __del__(self):

print('__del__')

n = node(0)

del n

# __del__

n1 = node(1)

n2 = node(2)

n1.add_child(n2)

del n1 # no output

n2.parent

del n2

在3.7.7中gc可以**迴圈引用物件;

但既然weakref存在,並且很多模組也在使用它,就目前的情況而言,在開發時使用weakref而不是依賴於gc是一件惠而不費的事情 。

for迴圈 while迴圈

迴圈結構 當重複執行相同的 或者是相似的 時。迴圈三要素 1 迴圈變數的宣告 用於控制迴圈次數的迴圈因子 2 迴圈條件 用於判斷是否執行相同或相似內容 迴圈體 的條件 3 迴圈變數的改變方向 向著迴圈結束的方向改變。1 for迴圈 語法 for 變數的宣告和初始化 迴圈條件 變數的改變方向 執行邏輯...

python while迴圈 for迴圈

1變數的初始化 while 條件2 條件滿足時候 執行該 條件滿足時候 執行該 3變數的更新 1 while 迴圈輸出1 100所有的數 while 迴圈輸出20次我愛你 迴圈輸出1 100累加和 1 100之間所有數的和 1變數的初始化 i 0 sum 0 儲存和 判斷條件 while i 100...

python while 迴圈 if 迴圈

python 程式設計中 while 語句用於迴圈執行程式,即在某條件下,迴圈執行某段程式,以處理需要重複處理的相同任務。其基本形式為 執行語句可以是單個語句或語句塊。判斷條件可以是任何表示式,任何非零 或非空 null 的值均為true。當判斷條件假false時,迴圈結束。執行流程圖如下 prin...