ITPub博客

首页 > 数据库 > 数据库开发技术 > python面向对象编程篇(15):弱引用的解析和简单实践

python面向对象编程篇(15):弱引用的解析和简单实践

原创 数据库开发技术 作者:程序员小钟同学 时间:2021-03-18 00:00:00 0 删除 编辑

个人其他博客地址:

简书:https://www.jianshu.com/u/d6960089b087

掘金:https://juejin.cn/user/2963939079225608

小钟同学 | 文 【原创】| QQ:308711822

  • 1:本文相关描述主要是个人的认知和见解,如有不当之处,还望各位大佬指正。
  • 2:关于文章内容,有部分内容参考自互联网,如有链接会声明标注;如没有及时标注备注的链接的,如有侵权请联系,我会立即删除处理哟。

在java中,按以往的学习可以得知,我们的Java有四种引用包括,引用的存在主要是和垃圾回收机制所关联。

  • 强引用 :只要引用存在,垃圾回收器永远不会回收
  • 软引用 :非必须引用,内存溢出之前进行回收
  • 弱引用 :第二次垃圾回收时回收
  • 虚引用 :垃圾回收时回收,无法通过引用取到对象值

以上是在java机制存在的,但是在python没有存在上面几种,有只是有弱引用的说法。

1、python中存在弱引用的作用

什么是弱引用:在python里面可以理解为是原对象的地址一个代理。

  • 原对象的地址一个代理
  • 弱引用(weak reference)是一个对象句柄。

弱引用存在的目的,其实也和我们的PVM的垃圾回收机制有关系。之前在说垃圾回收机制 XXXXXXXXXXXXXXXXXXXXX 中有介绍过,我们的pyton的回收垃圾机制是:

当对象的引用计数为0的时候,则进行回收。

所以python中弱引用的存在作用主要是:

  • 1、在不增加对象的引用计数个数的情况下获得对象的引用(弱引用并不占用计数次数)
  • 2、循环引用中减少循环引用,减少内存中不必要的对象存在的数量。
  • 3、可以主动观察对象的引用计数,进行回收

常见应用场景:

  • 双链表中使用,当头部指向改变,后面的对象就会被释放
  • 涉及到树和图的数据结构内部节点循环应用

2、弱引用创建

引用计数变化示例:

使用弱引用前:

import sys
import weakref
class A1:

    def __init__(self):
        self.name = "xiaozhong"
        self.age = 23

a = A1()
print(sys.getrefcount(a)) # 输出 2
b= a
print(sys.getrefcount(a))# 输出 3

使用弱引用后:

import sys
import weakref
class A1:

    def __init__(self):
        self.name = "xiaozhong"
        self.age = 23

a = A1()
print(sys.getrefcount(a))  # 输出 2
b=weakref.ref(a)
print(sys.getrefcount(a))  # 输出 2

PS:weakref.ref(a) 弱引用对象也是一个对象,也需要分配地址 所以:

import sys
import weakref
class A1:

    def __init__(self):
        self.name = "xiaozhong"
        self.age = 23

a = A1()
print(sys.getrefcount(a))
weakref_obj=weakref.ref(a)
print(sys.getrefcount(a))
print('a引用地址:',id(a))
print('弱引用对象的地址:',id(weakref_obj)) 

输出的结果为:

2
2
a引用地址:1943146576024
弱引用对象的地址:1943145890904

3、weakref.ref(obj) 调用对象的属性或方法

PS:weakref.ref(obj) 弱引用调用对象的属性或方法 对于的变量需加上()才变为对象

我们有了引用主要是为了调用相关的引用的对象的方法或属性。

import sys
import weakref
class A1:

    def __init__(self):
        self.name = "xiaozhong"
        self.age = 23

a = A1()
print(sys.getrefcount(a))
weakref_obj=weakref.ref(a)
print(sys.getrefcount(a))
print('a引用地址:',id(a))
print('weakref.ref(a)弱引用对象的地址:',id(weakref.ref(a)))
print('weakref_obj弱引用变量的地址:',id(weakref_obj))

输出的结果为:

2
2
a引用地址:2387711670424
weakref.ref(a)弱引用对象的地址:2387710985304
weakref_obj弱引用变量的地址:2387710985304

上面的代码:PS:这里获取到是一个地址,如果是想获取弱引用变量所指的对象,需要加上()才是得到对象 上面的代码可以理解为:

weakref_obj=weakref.ref(a)=a引用地址

如果我使用弱引用的weakref_obj的变量去调用A1的相关的变量的,此时会报错滴!

print(weakref_obj.name)

因为weakref_obj它的对象是指是一个地址,不是一个对象。

需要转化为一个对象之后才可以去调用,方式是加上 弱引用对象(),加上一个():

import sys
import weakref
class A1:

    def __init__(self):
        self.name = "xiaozhong"
        self.age = 23

a = A1()
print(sys.getrefcount(a))
weakref_obj=weakref.ref(a)
print(sys.getrefcount(a))
print('a引用地址:',id(a))
print('weakref.ref(a)弱引用对象的地址:',id(weakref.ref(a)))
print('weakref_obj弱引用变量的地址:',id(weakref_obj))
print(weakref_obj().name)

输出结果为:

2
2
a引用地址:2135388174488
weakref.ref(a)弱引用对象的地址:2135387489368
weakref_obj弱引用变量的地址:2135387489368
xiaozhong

4、weakref.proxy(obj)调用对象的属性或方法

PS:、weakref.proxy(obj)使用代理方式调用属性,不需要加()就可以充当是一个对象

import sys
import weakref
class A1:

    def __init__(self):
        self.name = "xiaozhong"
        self.age = 23

a = A1()
print(sys.getrefcount(a))




weakref_obj_proxy = weakref.proxy(a)   # 使用代理 引用计数不变

print(sys.getrefcount(a))
print('a引用地址:',id(a))
print(' weakref.proxy(a)弱引用对象的地址:',id( weakref.proxy(a)))
print('weakref_obj_proxy弱引用变量的地址:',id(weakref_obj_proxy))
print("查看最新的引用计数:",sys.getrefcount(a))
print('使用代理对象访问属性:',weakref_obj_proxy.name)

输出的结果为:

2
2
a引用地址:2162131944840
 weakref.proxy(a)弱引用对象的地址:2162132075608
weakref_obj_proxy弱引用变量的地址:2162132075608
查看最新的引用计数:2
使用代理对象访问属性: xiaozhong

4、weakref.ref(obj)和weakref.proxy(obj)  删除引用时候回调

proxy和ref 构造函数接受一个可选的回调函数,删除所引用的对象是会调用这个函数。

weakref.ref(obj)下的删除回调:

import sys
import weakref
class A1:

    def __init__(self):
        self.name = "xiaozhong"
        self.age = 23

    def __del__(self):
        """
        :return: delete massage
        "
""
        print("我被回收了")


a = A1()
print(sys.getrefcount(a))

def callback_ref(self):
    print("宁为玉碎,不为瓦全!")

weakref_obj_proxy = weakref.ref(a,callback_ref)   # 使用ref 引用计数不变
del a

另一种匿名函数的写法:

import sys
import weakref
class A1:

    def __init__(self):
        self.name = "xiaozhong"
        self.age = 23

    def __del__(self):
        """
        :return: delete massage
        "
""
        print("我被回收了")


a = A1()
print(sys.getrefcount(a))


weakref_obj_proxy = weakref.ref(a,lambda ref: print("宁为玉碎,不为瓦全!", ref))   # 使用ref 引用计数不变
del a

weakref.proxy(obj)下的删除回调:

import sys
import weakref
class A1:

    def __init__(self):
        self.name = "xiaozhong"
        self.age = 23

    def __del__(self):
        """
        :return: delete massage
        "
""
        print("我被回收了")


a = A1()
print(sys.getrefcount(a))

def callback_proxy(self):
    print("宁为玉碎,不为瓦全!")

weakref_obj_proxy = weakref.proxy(a,callback_proxy)   # 使用代理 引用计数不变
del a

5、查看一个对象被弱引用的次数,以及被弱引用的对象列表

5.1有回调函数的情况下:

import sys
import weakref
class A1:

    def __init__(self):
        self.name = "xiaozhong"
        self.age = 23

    def __del__(self):
        """
        :return: delete massage
        "
""
        print("我被回收了")


a = A1()
print('a当前引用计数',sys.getrefcount(a))

def callback_proxy(self):
    print("宁为玉碎,不为瓦全!")



weakref_obj_proxy_001 = weakref.proxy(a,callback_proxy)   # 使用代理 引用计数不变
weakref_obj_proxy_002 = weakref.proxy(a,callback_proxy)   # 使用代理 引用计数不变
weakref_obj_proxy_003 = weakref.proxy(a,callback_proxy)   # 使用代理 引用计数不变

print('weakref.proxy下==a对象被弱引用的次数',weakref.getweakrefcount(a))  # 1
print('weakref.proxy下==a被弱引用的对象列表',weakref.getweakrefs(a))

weakref_obj_ref_001 = weakref.ref(a,callback_proxy)   # ref 引用计数不变
weakref_obj_ref_002 = weakref.ref(a,callback_proxy)   # ref 引用计数不变
weakref_obj_ref_003 = weakref.ref(a,callback_proxy)   # ref 引用计数不变

print('weakref.ref下==a对象被弱引用的次数',weakref.getweakrefcount(a))  # 1
print('weakref.ref下==a被弱引用的对象列表',weakref.getweakrefs(a))

输出的结果为:

a当前引用计数 2
weakref.proxy下==a对象被弱引用的次数 3
weakref.proxy下==a被弱引用的对象列表 [<weakproxy at 0x000001BE19CC0138 to A1 at 0x000001BE19A17710>, <weakproxy at 0x000001BE19CC00E8 to A1 at 0x000001BE19A17710>, <weakproxy at 0x000001BE19AF4458 to A1 at 0x000001BE19A17710>]
weakref.ref下==a对象被弱引用的次数 6
weakref.ref下==a被弱引用的对象列表 [<weakref at 0x000001BE19CD9098; to 'A1' at 0x000001BE19A17710>, <weakref at 0x000001BE19CD9048; to 'A1' at 0x000001BE19A17710>, <weakref at 0x000001BE19CC0958; to 'A1' at 0x000001BE19A17710>, <weakproxy at 0x000001BE19CC0138 to A1 at 0x000001BE19A17710>, <weakproxy at 0x000001BE19CC00E8 to A1 at 0x000001BE19A17710>, <weakproxy at 0x000001BE19AF4458 to A1 at 0x000001BE19A17710>]
我被回收了

5.2 没有回调函数的情况下:

import sys
import weakref
class A1:

    def __init__(self):
        self.name = "xiaozhong"
        self.age = 23

    def __del__(self):
        """
        :return: delete massage
        "
""
        print("我被回收了")


a = A1()
print('a当前引用计数',sys.getrefcount(a))

def callback_proxy(self):
    print("宁为玉碎,不为瓦全!")



weakref_obj_proxy_001 = weakref.proxy(a)   # 使用代理 引用计数不变
weakref_obj_proxy_002 = weakref.proxy(a)   # 使用代理 引用计数不变
weakref_obj_proxy_003 = weakref.proxy(a)   # 使用代理 引用计数不变

print('weakref.proxy下==a对象被弱引用的次数',weakref.getweakrefcount(a))  # 1
print('weakref.proxy下==a被弱引用的对象列表',weakref.getweakrefs(a))

weakref_obj_ref_001 = weakref.ref(a)   # ref 引用计数不变
weakref_obj_ref_002 = weakref.ref(a)   # ref 引用计数不变
weakref_obj_ref_003 = weakref.ref(a)   # ref 引用计数不变

print('weakref.ref下==a对象被弱引用的次数',weakref.getweakrefcount(a))  # 1
print('weakref.ref下==a被弱引用的对象列表',weakref.getweakrefs(a))

输出的结果:

a当前引用计数 2
weakref.proxy下==a对象被弱引用的次数 1
weakref.proxy下==a被弱引用的对象列表 [<weakproxy at 0x000001A423844408 to A1 at 0x000001A423767710>]
weakref.ref下==a对象被弱引用的次数 2
weakref.ref下==a被弱引用的对象列表 [<weakref at 0x000001A423910098; to 'A1' at 0x000001A423767710>, <weakproxy at 0x000001A423844408 to A1 at 0x000001A423767710>]
我被回收了

:PS 有回调函数和没有回调函数的区别:

  • 有函数回调:weakref_obj_proxy_001 和 weakref_obj_proxy_002 和 weakref_obj_proxy_003地址不同
  • 没有函数回调:weakref_obj_proxy_001 和 weakref_obj_proxy_002 和 weakref_obj_proxy_003 是同一个地址

6、字典的弱引用:

为什么需要引入字典的弱引用:

import sys
import weakref
class A1:

    def __init__(self):
        self.name = "xiaozhong"
        self.age = 23

    def __del__(self):
        """
        :return: delete massage
        "
""
        print("我被回收了")


a = A1()
print('a当前引用计数',sys.getrefcount(a))
# 创建一个普通字典
mydict = {}
#  作为字典的 key, a 指向的对象引用计数会加 1, 变成 2
mydict[a] = "我是A对象"
print('a当前引用计数',sys.getrefcount(a))
# 删除 a, 对对象无影响
del a
print(mydict)

输出的结果为:

a当前引用计数 2
a当前引用计数 3
{<__main__.A1 object at 0x000001BFD6AFBA20>: '我是A对象'}
我被回收了

使用字典增加了引用计数,使用弱引用字典则不会增加:

import sys
import weakref
class A1:

    def __init__(self):
        self.name = "xiaozhong"
        self.age = 23

    def __del__(self):
        """
        :return: delete massage
        "
""
        print("我被回收了")


a = A1()
print('a当前引用计数',sys.getrefcount(a))
# 创建一个弱引用字典
mydict = weakref.WeakKeyDictionary()
#  作为字典的 key, a 指向的对象引用计数会加 1, 变成 2
mydict[a] = "我是A对象"
print('a当前引用计数',sys.getrefcount(a))
del a
print(mydict)

输出的结果:

a当前引用计数 2
a当前引用计数 2
我被回收了
<WeakKeyDictionary at 0x1bdfd61f9b0>

7、弱引用“皇帝和随从太监的故事”示例:

意思就是:我不存在了,你也别想存在。用户在循环应用的示例:

PS:请原谅我的用中文来表示,纯属为了说明清楚:

import sys
class 皇帝:

    def __del__(self):
        print("皇帝驾崩了!")
    def 下圣旨(self):
        print("皇帝要说话了!!!!!!!!!!")
        print("朕要今晚去XXX")

class 内监总管:

    def __init__(self,分配一个皇帝):
        self.内监总管服侍的皇帝 = 分配一个皇帝
        # self.内监总管服侍的皇帝 = weakref.proxy(分配一个皇帝, self.on_destroy)

    def on_destroy(self, effect):
        """
        销毁时会调用这个方法
        "
""
        pass
        print('effect销毁时会调用这个方法')

    def 说话(self):
        print("圣旨到!!!")
        try:
            self.内监总管服侍的皇帝.下圣旨()
        except:
            print("你的皇帝已经没了!!!!没人会听你的圣旨")

    def __del__(self):
        print("总管大人你也随着皇帝一起吧!")

# 建立一个皇帝对象
康熙皇帝 = 皇帝()
print("康熙皇帝的引用计数:",sys.getrefcount(康熙皇帝))
# 建立一个太监总管
不知道名称内监总管 = 内监总管(康熙皇帝)
不知道名称内监总管.说话()
print("康熙皇帝的引用计数:",sys.getrefcount(康熙皇帝))
del 康熙皇帝
print("=======================")
print("康熙皇帝驾崩了!!!!")
不知道名称内监总管.说话()

print("=======================")

输出的结果:

康熙皇帝的引用计数:2
圣旨到!!!
皇帝要说话了!!!!!!!!!!
朕要今晚去XXX
康熙皇帝的引用计数:3
=======================
康熙皇帝驾崩了!!!!
圣旨到!!!
皇帝要说话了!!!!!!!!!!
朕要今晚去XXX
=======================
总管大人你也随着皇帝一起吧!
皇帝驾崩了!

上面最诡异的地方是:

del 康熙皇帝 康熙皇帝驾崩了!!!!

圣旨到!!!

皇帝要说话了!!!!!!!!!!

朕要今晚去XXX

使用弱引用的话:

#!/usr/bin/evn python
# coding=utf-8
import weakref
import sys
class 皇帝:

    def __del__(self):
        print("皇帝驾崩了!")
    def 下圣旨(self):
        print("皇帝要说话了!!!!!!!!!!")
        print("朕要今晚去XXX")

class 内监总管:

    def __init__(self,分配一个皇帝):
        # self.内监总管服侍的皇帝 = 分配一个皇帝
        self.内监总管服侍的皇帝 = weakref.proxy(分配一个皇帝, self.on_destroy)

    def on_destroy(self, effect):
        """
        销毁时会调用这个方法
        "
""
        pass
        print('effect销毁时会调用这个方法')

    def 说话(self):
        print("圣旨到!!!")
        try:
            self.内监总管服侍的皇帝.下圣旨()
        except:
            print("你的皇帝已经没了!!!!没人会听你的圣旨")

    def __del__(self):
        print("总管大人你也随着皇帝一起吧!")

# 建立一个皇帝对象
康熙皇帝 = 皇帝()
print("康熙皇帝的引用计数:",sys.getrefcount(康熙皇帝))
# 建立一个太监总管
不知道名称内监总管 = 内监总管(康熙皇帝)
不知道名称内监总管.说话()
print("康熙皇帝的引用计数:",sys.getrefcount(康熙皇帝))
del 康熙皇帝
print("=======================")
print("康熙皇帝驾崩了!!!!")
不知道名称内监总管.说话()

print("=======================")

输出结果:

康熙皇帝的引用计数:2
圣旨到!!!
皇帝要说话了!!!!!!!!!!
朕要今晚去XXX
康熙皇帝的引用计数:2
皇帝驾崩了!
effect销毁时会调用这个方法
=======================
康熙皇帝驾崩了!!!!
圣旨到!!!
你的皇帝已经没了!!!!没人会听你的圣旨
=======================
总管大人你也随着皇帝一起吧!

注意点:

圣旨到!!!

你的皇帝已经没了!!!!没人会听你的圣旨

这个就正常了!!!

以上是个人对弱引用的总结

参考资料:

https://blog.csdn.net/z_feng12489/article/details/89433878


原文作者:程序员小钟同学;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2021-02-20

  • 博文量
    224
  • 访问量
    111703