Python 中弱引用的神奇用法与原理探析

本文深入探讨了Python中的弱引用(weakref)及其原理,揭示了如何在不增加引用计数的情况下管理和释放资源。通过一个数据缓存中间件Cacher的示例,说明弱引用如何解决资源泄露问题。弱引用允许我们在不产生引用的前提下关联对象,使得对象在无强引用时能自动释放,避免了手动引用计数的繁琐。文章还介绍了弱引用的典型用法,如WeakKeyDictionary和WeakValueDictionary,并剖析了弱引用的工作原理,包括其内部结构和观察者模式的实现。通过对弱引用的理解,读者可以更好地利用这一特性来优化框架和中间件的设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Python 中弱引用的神奇用法与原理探析

弱引用可以在不产生引用计数的前提下,对目标对象进行管理,常用于框架和中间件中。弱引用看起来很神奇,其实设计原理是非常简单的观察者模式。

作者:佚名来源:Python开发者|2021-12-09 15:45

 收藏

  分享

 背景

开始讨论弱引用( weakref )之前,我们先来看看什么是弱引用?它到底有什么作用?

假设我们有一个多线程程序,并发处理应用数据:

 
  1. # 占用大量资源,创建销毁成本很高  
  2. class Data:  
  3.     def __init__(self, key):  
  4.         pass 

应用数据 Data 由一个 key 唯一标识,同一个数据可能被多个线程同时访问。由于 Data 需要占用很多系统资源,创建和消费的成本很高。我们希望 Data 在程序中只维护一个副本,就算被多个线程同时访问,也不想重复创建。

为此,我们尝试设计一个缓存中间件 Cacher :

 
  1. import threading  
  2. # 数据缓存  
  3. class Cacher:  
  4.     def __init__(self):  
  5.         self.pool = {}  
  6.         self.lock = threading.Lock()  
  7.     def get(self, key):  
  8.         with self.lock:  
  9.             data = self.pool.get(key)  
  10.             if data:  
  11.                 return data  
  12.             self.pool[key] = data = Data(key)  
  13.             return data 

Cacher 内部用一个 dict 对象来缓存已创建的 Data 副本,并提供 get 方法用于获取应用数据 Data 。get 方法获取数据时先查缓存字典,如果数据已存在,便直接将其返回;如果数据不存在,则创建一个并保存到字典中。因此,数据首次被创建后就进入缓存字典,后续如有其它线程同时访问,使用的都是缓存中的同一个副本。

感觉非常不错!但美中不足的是:Cacher 有资源泄露的风险!

因为 Data 一旦被创建后,就保存在缓存字典中,永远都不会释放!换句话讲,程序的资源比如内存,会不断地增长,最终很有可能会爆掉。因此,我们希望一个数据等所有线程都不再访问后,能够自动释放。

我们可以在 Cacher 中维护数据的引用次数, get 方法自动累加这个计数。于此同时提供一个 remove 新方法用于释放数据,它先自减引用次数,并在引用次数降为零时将数据从缓存字段中删除。

线程调用 get 方法获取数据,数据用完后需要调用 remove 方法将其释放。Cacher 相当于自己也实现了一遍引用计数法,这也太麻烦了吧!Python 不是内置了垃圾回收机制吗?为什么应用程序还需要自行实现呢?

冲突的主要症结在于 Cacher 的缓存字典:它作为一个中间件,本身并不使用数据对象,因此理论上不应该对数据产生引用。那有什么黑科技能够在不产生引用的前提下,找到目标对象吗?我们知道

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值