Python weakref (弱引用 ) 教程
<blockquote><p>原文:https://blog.csdn.net/NeverLate_gogogo/article/details/107021695<br>
本文有删改</p>
</blockquote>
<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>前言</li><li>一、变量<ul><li>1.1 变量是什么?</li><li>1.2 <code>==</code>和<code>is</code>之间的比较</li></ul></li><li>二、del与垃圾回收机制</li><li>三、弱引用<ul><li>3.1 弱引用是什么?</li><li>3.2 弱引用介绍与使用</li><li>3.3 弱引用使用举例</li></ul></li><li>四、<code>weakref.ref()</code> 和<code>weakref.proxy()</code> 的区别</li></ul></div><p></p>
<h1 id="前言">前言</h1>
<p>首先提一点:大家遇到python模块的使用问题,尽可能去 python document去找答案。</p>
<p>但是关于weakref,官网上给的例子,并不能让我们理解这个弱引用。<br>
于是在网上查了一些资料,也是比较模糊。</p>
<p>于是我还是从变量到垃圾回收再到若弱引用讲起这件事吧。因为他们是息息相关的,只有l理解了变量的引用和垃圾回收才会 更好的 理若引用的概念。</p>
<p>然后最后我再举2个例子,说明弱引用是怎么体现出来的。</p>
<h1 id="一变量">一、变量</h1>
<h2 id="11-变量是什么">1.1 变量是什么?</h2>
<p>变量是一个对象别名,可以理解成变量是贴在对象上的一个标签,所以当执行</p>
<p><code>my_list = </code><br>
其实就是在 <code></code>这个对象上贴了一个标签 <code>my_list</code>,我们可以通过这个标签来找到对象,进而可以操作对象。</p>
<p>那么我又执行:</p>
<p><code>my_list2 = my_list</code><br>
这个时候发生了什么呢,其实就是又给<code></code>这个对象添加了一个标签。当然,这种说法是只限于对可变对象的操作。我们可以参考下图:</p>
<p><img src="https://img2020.cnblogs.com/blog/746820/202105/746820-20210531143845769-366491475.png" alt="image" loading="lazy"></p>
<p>每个变量都有 标识 、类型 和 值。对象一旦创建,它的标识绝不会变;可以把标识理解为对象在内存中的地址。<code>is</code> 运算符比较两个对象的标识;<code>id()</code> 函数返回对象标识的整数表示。</p>
<p>因此当我们对上面执行 <code>id(my_list1)</code> 与 <code>id(my_list)</code> 他们返回的值是一样的,因为他们指向同一个对象,<code>my_list</code>与<code>my_list1</code>只是标签而已。</p>
<h2 id="12和is之间的比较">1.2 <code>==</code>和<code>is</code>之间的比较</h2>
<p><code>==</code> 运算符比较两个对象的值(对象中保存的数据),而 <code>is</code> 比较对象的标识。</p>
<p></p>
<p><code>is</code> 运算符比 <code>==</code> 速度快,因为它不能重载.接比较两个对象的 整数 ID。</p>
<p><code>a == b</code> 是语法糖,等同于 <code>a.__eq__(b)</code>。</p>
<p><code>__eq__</code> 方法继承自 <code>object</code>, 比较两个对象的 ID,结果与 <code>is</code> 一样。但是多数内置类型使用更有意义的方式覆盖了 <code>__eq__</code> 方法,会考虑对象属性的值。相等性测试可能涉及大量处理工作,例如,比较大型集合或嵌套层级深的结构时。</p>
<p></p>
<h1 id="二del与垃圾回收机制">二、del与垃圾回收机制</h1>
<blockquote>
<p>这里我们只讨论引用计数规则的垃圾回收机制</p>
</blockquote>
<p><strong>python中对象绝不会自行销毁</strong>;然而,无法得到对象时,可能会被当作垃圾回收。无法得到对象包括两种:<br>
①没有人引用这个对象了,也就是说这个对象身上被贴的标签都没有了,这时候我们其实就找不到这个对象了;<br>
②相互引用</p>
<p></p>
<p><code>del</code> 语句删除名称(也就是我们说的标签),而不是对象。当我们把贴在对象身上的标签全部删除了,这时候python垃圾回收机制的引用计数(可以理解为贴标签计数)检测到引用此对象的次数为0,那么就触发了垃圾回收机制,销毁此对象。</p>
<p>因此del命令并不会删除对象,而是当del删除了对象的最后一个引用时,会触发垃圾回收机制,回收器将对象销毁。这个概念要搞清。(看例1的代码)</p>
<p></p>
<p>重新绑定也可能会导致对象的引用数量归零,导致对象被销毁。什么意思呢?</p>
<p>我们执行下面的代码:</p>
<pre><code class="language-python">my_list =
my_list =
</code></pre>
<p>这个时候,对象<code></code>就被销毁了,为什么?</p>
<p>因为my_list这个标签从对象<code></code>上被撕下来了,贴到了对象<code></code>上。这时候对象<code></code>的引用计数是不是就为0了,这时候就触发垃圾回收机制,将<code></code>这个对象给销毁了。</p>
<p>所以说,重新绑定也可能导致对象被销毁</p>
<p>(可以看例1的代码)</p>
<pre><code class="language-python"># 代码示例1
>>> import weakref
>>> s1 = {1,2,3} # 给对象{1,2,3}贴了一个标签s1
>>> def bye(): # 对象{1,2,3}被销毁时,调用这个函数
... print("拜拜,你被销毁了")
...
>>> ender = weakref.finalize(s1,bye) # 绑定回调函数
>>> ender.alive # 看对象{1,2,3}仍然存活
True
>>> s1 = {4,5,6} # 当标签s1从{1,2,3}上撕下来,对象{1,2,3}被销毁了
拜拜,你被销毁了
>>> ender.alive
False
</code></pre>
<p>如果两个对象相互引用(不懂相互引用的可以自行学习一下),当它们的引用只存在二者之间时,垃圾回收程序会判定它们都无法获取,进而把它们都销毁。</p>
<p><code>__del__</code> 特殊方法.不会销毁实例,不应该在代码中调用。即将销毁实例时,Python 解释器会调用 <code>__del__</code> 方法,给实例最后的机会,释放外部资源。 参考标准库del特殊方法.</p>
<p>在 CPython 中,垃圾回收使用的主要算法是引用计数。</p>
<p>实际上,每个对象都会统计有多少引用指向自己.</p>
<p>当引用计数归零时,对象立即就被销毁:CPython 会在对象上调用 <code>__del__</code> 方法(如果定义了),然后释放分配给对象的内存。</p>
<p>CPython 2.0 增加了分代垃圾回收算法,用于检测引用循环中涉及的对象组——如果一组对象之间全是相互引用,即使再出色的引用方式也会导致组中的对象不可获取。</p>
<p>Python 的其他实现有更复杂的垃圾回收程序,而且不依赖引用计数,这意味着,对象的引用数量为零时可能不会立即调用 <strong>del</strong> 方法。</p>
<pre><code class="language-python"># 代码示例2
# 使用 weakref.finalize 注册一个在销毁对象时调用的回调函数。
In : import weakref
In : s1 = {1,2,3}
In : s2 =s1# 指向同一个集合.
In : def bye():# 回调函数一定不能是要销毁的对象的绑定方法(类方法),否则会有一个指向对象的引用。
...: print("拜拜,你被销毁了")
...:
In : ender = weakref.finalize(s1,bye)# 注册回调, 并返回一个变量,判断是否销毁。
In : ender.alive
Out: True
In : del s1
In : ender.alive # 说明 del s1 是删除引用,而不是对象。
Out: True
In : s2 = 'spam'# 重新绑定 s2 后,表示 {1,2,3} 无法获取 (引用计数为0),此时调用 bye 回调函数.
拜拜,你被销毁了
In : ender.alive# ender 为 弱引用, 不在 计数 范围内.
Out: False
</code></pre>
<p>所以,每个引用就相当于一个标签,通过这个标签我们可以找到这个对象。一旦这些标签别撕没了,也就是对象的引用为0的时候,就出触发python的垃圾回收的机制。</p>
<p></p>
<h1 id="三弱引用">三、弱引用</h1>
<p>这个时候可以引出我们心心念的弱引用了。</p>
<h2 id="31-弱引用是什么">3.1 弱引用是什么?</h2>
<p>在上文,我们看到,当执行 <code>my_list=</code>时,这时候就相当于给对象<code></code>加了一个强引用(标签)。再执行<code>my_list1 = my_list</code>时,那么对象<code></code>的强引用个数就变为了2,我们想要触发python的垃圾回收机制销毁对象<code></code>时,就必须把两个强引用<code>my_list</code>和<code>my_list1</code>都删除。</p>
<p>这时候,<code>my_list2 = </code>这种方式,我不想使·my_list2·成为对象的强引用,那么我就可以把·my_list2·定义为一个弱引用,这时候,就当发生贴标签的操作时,就会是一个弱引用。而弱引用不会影响垃圾回收的计数。也就是说,<strong>一个对象,只要强引用个数为0,就会触发python的垃圾回收机制,而不管你有多少个弱引用,都是没关系的。</strong></p>
<p></p>
<h2 id="32-弱引用介绍与使用">3.2 弱引用介绍与使用</h2>
<p>弱引用不会增加对象的引用数量。引用的目标对象称为 所指对象 (referent)。因此,弱引用不会妨碍所指对象被当作垃圾回收。</p>
<p>弱引用在缓存应用中很有用,因为不想仅因为被缓存引用着而始终保存缓存对象。</p>
<p>使用 <code>weakref.ref</code> 实例可以获取所指对象。如果对象存在,调用弱引用可以获取对象;否则返回 <code>None</code> 。</p>
<p><code>weakref.ref</code> 类其实是低层接口,供高级用途使用,多数程序最好使用 <strong>weakref 工具集</strong> 和 <code>finalize</code> 。</p>
<p>weakref 工具集合:</p>
<ul>
<li><code>WeakKeyDictionary</code>:</li>
<li><code>WeakValueDictionary</code>: 这是一种可变映射,里面的值是对象的弱引用。被引用的对象在程序中的其他地方被当作垃圾回收后,对应的键会自动从 <code>WeakValueDictionary</code> 中删除。因此,<code>WeakValueDictionary</code> 经常用于缓存。</li>
<li><code>WeakSet</code>: 保存元素弱引用的集合类。元素没有强引用时,集合会把它删除。</li>
<li><code>finalize</code> (内部使用弱引用)</li>
</ul>
<p>如果一个类需要知道所有实例,一种好的方案是创建一个 WeakSet 类型的类属性,保存实例的引用。</p>
<p>如果使用常规的 <code>set</code> ,实例永远不会被垃圾回收,因为类中有实例的强引用,而类存在的时间与 Python 进程一样长,除非显式删除类。</p>
<p></p>
<p>弱引用局限:</p>
<ul>
<li>基本的 list 和 dict 实例不能作为所指对象, 但是它们的子类可以作为弱引用所指对象.</li>
<li>基本的 int 、 list 、 tuple 、string 、dict 实例不能作为弱引用的目标。</li>
<li>但是 set 实例可以作为所指对象。</li>
<li>但是, str 、 dict 、list 的子类实例 和 用户自定义的类型实例 可以作为弱引用所指对象.</li>
<li>然而, int 、 tuple 的子类实例 也不能作为弱应用对象.</li>
</ul>
<p></p>
<h2 id="33-弱引用使用举例">3.3 弱引用使用举例</h2>
<p>任何的数据结构都是可以弱引用的,我们要多利用weakref包中提供的工具类</p>
<pre><code class="language-python"># 前提: Python 控制台会自动把 _ 变量绑定到结果不为 None 的表达式结果上。
In : import weakref
In : a_set = {0,1}
In : wref = weakref.ref(a_set)# 弱引用对象 wref
In : wref
Out: <weakref at 0x10f29aea8; to 'set' at 0x10e906f28>
In : wref()# 返回的是被引用的对象,{0, 1}。因为是控制台会话,所以 {0, 1} 会绑定给 _ 变量。
Out: {0, 1}
In : a_set = {2, 3, 4} # a_set 不再指代 {0, 1} 集合,因此集合的引用数量减少了。但是 _ 变量仍然指代它。
In : wref()
Out: {0, 1}
In : wref() is None# 计算这个表达式时,{0, 1} 存在,因此 wref() 不是 None。但是,随后 _ 绑定到结果值 False。现在 {0, 1} 没有强引用了。
Out: False
In : wref() is None# 因为 {0, 1} 对象不存在了,所以 wref() 返回 None。
Out: Ture
</code></pre>
<p></p>
<p>WeakValueDictionary 示例:</p>
<pre><code class="language-python">class Cheese:
def __init__(self, kind):
self.kind = kind
def __repr__(self):
return 'Cheese(%r)' % self.kind
# 执行:
In : import weakref
In : stock = weakref.WeakValueDictionary()# 创建弱引用字典实例。
In : catalog =
In : for cheese in catalog:
...: stock = cheese# 名称映射到实例. [弱引用]
...:
In : sorted(stock.keys())
Out: ['Brie', 'Parmesan', 'Read Leicester', 'Tilsit']
In : del catalog
In : sorted(stock.keys())# 为什么还剩一个? 因为临时变量。
Out: ['Parmesan']
In : del cheese
In : sorted(stock.keys())# 临时变量删除后,为空.
Out: []
</code></pre>
<p>下面这个例子,是我自己编写的,大家要搞懂下面的这个例子,相信对弱引用,就有些理解了。</p>
<pre><code class="language-python">
>>> import weakref
>>>
>>> class C: # 这里新建一个类,因为WeakValueDictionary()
... def __init__(self, value): # 要求value是一个obj
... self.value = value
...
>>> def test_weak_value_dict():
... d= weakref.WeakValueDictionary()
... k1 = 'test1'
... v1 = C(1) # 这时候C(1)是有一个强引用的:v1
... d = v1 # 这个语句也就是字典赋值,但是由于我们用的
... print(d) # WeakValueDictionary(),所以字典里的是弱引用
... del v1 # 这时候删除了C(1)唯一的强引用 v1,因此
... print(d) # WeakValueDictionary()里面 k1,v1 这个键值对消失了
...
>>> test_weak_value_dict()
<__main__.C object at 0x000001793E545198>
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in test_weak_value_dict
File "C:\Users\ASUS\Anaconda3\lib\weakref.py", line 137, in __getitem__
o = self.data()
KeyError: 'test1'
>>>
</code></pre>
<p>我们如果使用dict代替WeakValueDictionary(),会发生什么呢?</p>
<p>dict内部的引用都是强引用,因此我们删除了C(1)的强引用v1,还有dict里面对C(1)的强引用,因此c(1)不会被销毁</p>
<pre><code class="language-python">>>> import weakref
>>>
>>> class C:
... def __init__(self, value):
... self.value = value
...
>>> def test_weak_value_dict():
... d= dict()
... k1 = 'test1'
... v1 = C(1)
... d = v1
... print(d)
... del v1
... print(d)
...
>>> test_weak_value_dict()
<__main__.C object at 0x000001793E545390>
<__main__.C object at 0x000001793E545390>
</code></pre>
<p>可以看到没有发生报错,就是因为dict里面的引用为强引用</p>
<p>再看下面一个例子,直接报错,搞明白原因了吗?</p>
<pre><code class="language-python">>>> import weakref
>>>
>>> class C:
... def __init__(self, value):
... self.value = value
...
>>> def test_weak_value_dict():
... d= weakref.WeakValueDictionary()
... k1 = 'test1'
... d = C(1)
... print(d)
...
>>> test_weak_value_dict()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in test_weak_value_dict
File "C:\Users\ASUS\Anaconda3\lib\weakref.py", line 137, in __getitem__
o = self.data()
KeyError: 'test1'
</code></pre>
<p>为什么报错:keyerror?</p>
<p>因为这条记录已经被删除了。为什么?</p>
<p>我们看对C(1)的引用有谁? 只有WeakValueDictionary()中的弱引用,根本没有强引用。</p>
<p>所以触发垃圾回收机制将C(1)销毁了,因此WeakValueDictionary()关于C(1)的记录也就删除了。</p>
<p></p>
<h1 id="四weakrefref-和weakrefproxy-的区别">四、<code>weakref.ref()</code> 和<code>weakref.proxy()</code> 的区别</h1>
<p>其实这两个只是使用上有稍微的区别,<code>proxy()</code>算是给用户提供一个更加简洁的接口,看下面的代码就懂了。</p>
<pre><code class="language-python">>>>
>>> import weakref
>>> class C:
... def __init__(self, value):
... self.value = value
>>> c_obj = C(1)
>>> ref = weakref.ref(c_obj)
>>> ref()
<__main__.C object at 0x000001793E5454A8>
>>> ref().value
1
>>>
>>> proxy = weakref.proxy(c_obj)
>>> proxy
<weakproxy at 0x000001793DF55CC8 to C at 0x000001793E5454A8>
>>> proxy.value
1
</code></pre>
<p>我们在使用<code>weak.ref</code>时,返回值ref,需要执行<code>ref()</code>才是弱引用的对象,<code>ref()</code> 相当于 <code>c_obj</code></p>
<p>而<code>weakref.proxy</code>的返回值直接就是弱引用的对象,返回值<code>proxy</code>直接相当于<code>c_obj</code></p>
<p>————————————————<br>
版权声明:本文为CSDN博主「NeverLate_gogogo」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。<br>
原文链接:https://blog.csdn.net/NeverLate_gogogo/article/details/107021695</p><br><br>
来源:https://www.cnblogs.com/marsggbo/p/14831456.html
頁:
[1]