Python知識分享網 - 專業(yè)的Python學習網站 學Python,上Python222
Python中的弱引用與基礎類型支持情況探究
發(fā)布于:2023-07-25 11:36:00

背景

最近有一個業(yè)務場景需要用Python自行實現(xiàn)一個簡單的LRU cache,不可避免的接觸到了弱引用這一概念,這里記錄一下。

 

強引用

Python內存回收由垃圾回收器自動管理,當一個對象的引用計數(shù)歸0時,其內存就可能被回收掉,而引用計數(shù)器的數(shù)值其實就是代表有多少個強引用指向該對象,我們日常寫的Python代碼如果沒有使用到weakref模塊一般都只會涉及到強引用。
可以通過sys.getrefcount查看對象的引用計數(shù),如以下代碼:

 

import sys

alist = [1, 2, 3] # alist引用計數(shù)=1
print(sys.getrefcount(alist)) # 包括getrefcount本身新增的強引用,輸出2
blist = alist
print(sys.getrefcount(alist)) # 新增blist強引用,輸出3
print(blist) # 輸出[1, 2, 3]
del blist
print(sys.getrefcount(alist)) # 刪除了blist,強引用-1, 輸出2

 

弱引用

與強引用相對,弱引用并不會影響對象的引用計數(shù),也就是說其不影響對象是否被回收的判定,如以下代碼:

 

import sys
import weakref

class tlist(list): # list本身不支持弱引用,但其子類支持
    pass

alist = tlist([1, 2, 3]) # alist引用計數(shù)=1
print(sys.getrefcount(alist)) # 輸出2
bref = weakref.ref(alist) # bref為對alist對象的弱引用
print(bref()) # 返回弱引用對象,輸出: [1, 2, 3]
print(sys.getrefcount(alist)) # 由于弱引用不影響引用計數(shù),依然輸出2
del alist # 刪除alist,對象引用計數(shù)變?yōu)?
print(bref()) # 由于bref指向的對象已無任何強引用,返回None

 

如上代碼所示弱引用不會影響對象的引用計數(shù),亦即不會影響對象內存的回收,但是這里碰到一個引人疑惑的點,就是Python中的基本數(shù)據類型對弱引用的支持分了三種情況。

 

基礎類型對于弱引用支持情況

基礎類型int、list、dict、tuple、str不支持弱引用,對其執(zhí)行弱引用會報錯:

 

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-9daeb515714d> in <module>
----> 1 weakref.ref(alist)

TypeError: cannot create weak reference to 'list' object

 

可以通過__weakrefoffset__查看類型是否支持弱引用,該變量表示弱引用指針相對對象起始地址的偏移量,>0表示支持弱引用:

 

In [1]: int.__weakrefoffset__
Out[1]: 0
In [2]: str.__weakrefoffset__
Out[2]: 0
In [3]: tuple.__weakrefoffset__
Out[3]: 0
In [4]: list.__weakrefoffset__
Out[4]: 0
In [5]: dict.__weakrefoffset__
Out[5]: 0
In [6]: set.__weakrefoffset__
Out[6]: 192

 

官方文檔中介紹:

 

Several built-in types such as list and dict do not directly support weak references but can add support through subclassing:
CPython implementation detail: Other built-in types such as tuple and int do not support weak references even when subclassed.

 

總結基礎類型對弱引用的支持分為以下三種情況(for python3.8):

1、對于list、dict、str本身不支持弱引用,但可以通過創(chuàng)建子類的方式對其進行弱引用

2、對于int、tuple本身及其子類均不支持弱引用

3、set直接支持弱引用

這又是出于什么考慮?通過一番探究得出以下可能原因:

1、絕大部分場景下,基礎類型使用并不涉及到弱引用,所以基礎類型不支持弱引用可以有效避免相應的overhead。

2、弱引用添加于Python2.1,所以對于之后添加的類型(包括object、type、set等)默認都是支持弱引用的,除非有明確的理由不這么做。

3、對于list、dict、int、str、tuple這些2.1之前的基礎類型為了兼容性考慮均默認不支持弱引用,而set添加與2.3,因此其直接支持弱引用。

4、int、str、tuple這些不可變對象,在CPython解釋器中會有特殊的處理邏輯:

     4.1 如[-5, 256]范圍的小整數(shù)池一開始就被創(chuàng)建好了,在程序的整個生命周期無論是否被實際引用都不會被回收。

     4.2 又如對于同一個compilation unit的tuple對象,如果取值相同,編譯器會將獨立的多個相同的tuple對象處理為指向同一個對象的多個強引用。
在這些情況下使用弱引用并沒有什么明顯的好處,反而額外引入了overhead,綜合考慮直接對其不支持弱引用。

5、出于CPython的具體實現(xiàn)細節(jié),對于int、tuple的子類也不支持弱引用。

 

 

轉載自:https://www.cnblogs.com/AcAc-t/p/python_weakref_study.html