28、Python之面向对象:强引用与弱引用,舍不得与留不住

南宫理的日志录 2024-09-27 10:23:20
引言

大都好物不坚牢,彩云易散琉璃脆。——白居易《简简吟》

上一篇文章中已经简单介绍了Python中对象的生命周期,对象在内存中是动态变化的,创建、使用、销毁,都跟引用息息相关。

在更早的文章中也提到过Python最基本的垃圾回收是基于引用计数的。对象只有能被引用到,才是可用的,也就是“对象的可达性”。但是,并不是每一个引用都会使得被引用对象的引用计数增加,引用有强弱之分。今天的文章就是聚焦于对象的引用,聊聊引用的强与弱。

变量、强引用以及del操作

一个看似简单的“变量”的概念,其实,前面已经花了几篇文章的篇幅进行解释。截止目前,应该有如下关于“变量”的理解:

1、Python中一切皆为对象,所以,所有的变量赋值,其实都是引用赋值。

2、Python中变量是标签而不是盒子,可以粗略理解为变量本身记录的是要引用对象的地址。

3、要真正理解变量的赋值操作,关键在于区分是对变量重新赋值,从而发生引用关系的变化?还是引用关系不变,只是对对象内容的修改。

有了这些理解之后,就能很清楚地知道,变量与对象之间是引用的关系。而且,这种引用关系,其实就是强引用,会涉及到对象的引用计数的变化。

而所谓的del操作,新手可能会误以为是删除一个对象,其实本质上是解除引用关系,变量这个绑定在对象上的标签,被解除了。但是,对象本身不一定被删除,除非本次del操作后,对象的引用计数为0了,或者对象不可达了,才有可能触发对象的垃圾回收。

以实际代码为例,进行说明:

执行结果:

弱引用的引入

一旦解除所有引用,则对象就无法被访问了,对象会自然进入垃圾回收的流程。

但是,如果不解除所有引用,又会组织垃圾回收,导致内存一直不会被释放。

存在这样一种需求,虽然引用了对象,但是最好不要影响垃圾回收的处理。比如:缓存,虽然持有了对象的引用,但是对象可以被释放,我缓存失效就完事儿了,但不能所有加入缓存的对象,都不能被垃圾回收了,这显然不合理。代理模式、观察者模式也是同样的逻辑,对象本身不需要存在了,但是,总不能因为我加了个代理或者观察者,就导致了对象多了引用,无法被垃圾回收了。

为了应对这些特殊场景,弱引用的机制应运而生。

需要说明的是,弱引用不是Python的原创,其他面向对象的语言中也有相关的约定、实现。

Python中的弱引用,使用很简单,直接使用Python中的标准库的weakref模块,即可创建对象的弱引用,从而不增加对象的引用计数,因此也不会阻止对象的垃圾回收进程。

首先通过代码简单演示一下weakref模块的使用:

执行结果:

通过代码的执行演示,可以看到:

1、weakref.ref()函数会创建一个对象的弱引用。

2、弱引用是一个可调用对象,类似于函数调用时,会获得引用的对象,可以像正常使用对象一样,访问对象的属性、方法等。

3、弱引用不增加引用计数。

4、强引用解除时,可能导致对象被垃圾回收,弱引用执行调用操作会返回None。所以,通过弱引用使用对象时,最好加上是否为None的安全判断。

只是通过上面的代码示例,会觉得弱引用没有什么价值,毕竟如果要访问对象,直接使用变量赋值的强引用即可,而且使用起来更加方便。

其实,前面已经提到过弱引用的作用是避免影响引用计数及垃圾回收。

其典型的使用场景,有缓存、代理及观察者等。

弱引用代理

首先看下弱引用代理的使用:

通过weakref.proxy()可以创建对象的代理,代理与被引用对象之间也是弱引用的关系,使用代理就好像使用被引用对象本身一样方便。

代码示例:

执行结果:

可以看到,弱引用的代理,可以像直接使用被引用对象一样,而且不增加被引用对象的引用计数。

被引用对象被释放时,会触发创建代理时注册的回调函数,从而实现一些对象释放时需要的扩展功能。

弱引用缓存

接下来看一下基于弱引用自己实现一个缓存的功能:

执行结果:

弱引用观察者模式

最后再通过弱引用实现一个观察者模式的效果:

我们通过代码模拟一个打工人干活,10个领导围观的效果:

执行结果:

可以看到,通过弱引用实现的观察者模式,也不会影响到对象的垃圾回收。

总结

本文主要介绍了变量与对象的引用关系,强引用与弱引用的区别,以及基于弱引用在代理模式、观察者模式、缓存中的使用。

感谢您的拨冗阅读,如果能够对您稍微有点帮助,那就是本文的最大价值了。

0 阅读:0

南宫理的日志录

简介:深耕IT科技,探索技术与人文的交集