Python 3.12 Special Attribute - 12 - __weakref__

张开发
2026/4/13 0:55:23 15 分钟阅读

分享文章

Python 3.12 Special Attribute - 12 - __weakref__
Python 3.12 Special Attribute -__weakref____weakref__是 Python 中用于支持弱引用weak reference的内部特殊属性。它不是一个需要手动设置的属性而是由 Python 解释器自动为类的实例添加的一个隐藏字段用于存储与该实例关联的弱引用列表或单个弱引用。弱引用允许你引用一个对象而不增加其引用计数从而避免循环引用导致的内存泄漏并且当被引用对象被销毁时弱引用会自动失效返回None。理解__weakref__有助于掌握 Python 的内存管理机制、循环引用的处理以及weakref模块的工作原理。本文将详细解析__weakref__的定义、作用、底层实现并通过多个示例演示如何使自定义类支持弱引用、如何使用weakref模块以及__weakref__与__slots__的关系。1.__weakref__的基本概念定义__weakref__是 Python 实例的一个内部属性通常是一个PyWeakReference对象或链表的头指针用于支持对该实例的弱引用。自动添加默认情况下Python 会为所有类除非被__slots__排除的实例自动添加__weakref__属性以支持弱引用。可见性在普通 Python 代码中你可以通过obj.__weakref__访问它但通常不需要它的值通常是None或一个weakref.ref对象或者weakref链表的内部结构。与weakref模块的关系weakref.ref(obj)会创建一个弱引用对象该对象内部会与obj.__weakref__进行关联以便在obj被销毁时自动将弱引用失效。示例classMyClass:passobjMyClass()print(obj.__weakref__)# None 没有弱引用时importweakref rweakref.ref(obj)print(obj.__weakref__)# weakref at 0x...; to MyClass at 0x...2. 为什么需要__weakref__弱引用是解决循环引用问题的关键工具。当两个对象相互引用时即使没有外部引用它们的引用计数也不会变为 0导致内存泄漏。垃圾回收器虽然能处理部分循环引用但效率较低。使用弱引用可以让一个对象引用另一个对象而不增加引用计数从而打破循环。__weakref__是 Python 内部实现弱引用的基础设施。每个支持弱引用的实例都拥有一个__weakref__槽位用于存储所有指向它的弱引用对象通过链表。当实例被销毁时Python 会遍历这个链表将所有弱引用对象的回调函数触发并将它们置为无效后续访问返回None。3.__weakref__与__slots__的关系默认情况下Python 会在每个实例中分配__weakref__空间一个指针。如果你定义了一个类并使用__slots__并且希望该类的实例支持弱引用则必须显式地将__weakref__包含在__slots__元组中。否则实例将不支持弱引用试图创建弱引用会引发TypeError。classWithSlots:__slots__(x,y)# 没有 __weakref__def__init__(self,x,y):self.xx self.yy objWithSlots(1,2)importweakref refweakref.ref(obj)# TypeError: cannot create weak reference to WithSlots object修复方法classWithSlotsAndWeakref:__slots__(x,y,__weakref__)def__init__(self,x,y):self.xx self.yy objWithSlotsAndWeakref(1,2)refweakref.ref(obj)# 正常4. 底层实现机制CPython在 CPython 中每个PyObject所有 Python 对象的基结构包含一个字段ob_refcnt引用计数和ob_type类型指针。对于实例对象是否拥有__weakref__取决于其类型对象PyTypeObject中的tp_weaklistoffset字段。默认情况对于普通类没有__slots__tp_weaklistoffset被设置为实例内存布局中__weakref__指针的偏移量。因此每个实例都有一个__weakref__指针。__slots__情况如果类定义了__slots__且没有包含__weakref__则tp_weaklistoffset为 0表示不支持弱引用。如果包含了__weakref__则会分配该槽位并设置tp_weaklistoffset。弱引用链当一个弱引用对象wr被创建并指向obj时wr会被插入到obj.__weakref__指向的链表中。当obj的引用计数变为 0 并被销毁时Python 会遍历这个链表调用每个弱引用对象的回调如果有并将它们标记为无效。此后访问wr()会返回None。内存布局无__slots__的实例PyObject_HEAD__dict__指针 __weakref__指针。有__slots__且包含__weakref__的实例PyObject_HEAD 槽位指针包括__weakref__指针。5. 使用场景与示例5.1 基本弱引用使用importweakrefclassData:def__init__(self,value):self.valuevalue dData(42)rweakref.ref(d)print(r())# __main__.Data object at ...deldprint(r())# None逐行解析行代码解释1导入weakref模块提供弱引用支持。3-5定义Data类普通类实例自动支持弱引用。7创建实例d引用计数为 1。8创建弱引用rr指向d但不增加引用计数。9通过弱引用获取对象返回d本身如果还存活。10删除强引用d引用计数变为 0对象被销毁。11再次访问弱引用返回None。为什么这样写演示弱引用的基本行为不阻止对象被销毁且在对象销毁后自动失效。5.2 使用__weakref__查看内部classDemo:passobjDemo()print(obj.__weakref__)# Noneimportweakref rweakref.ref(obj)print(obj.__weakref__)# weakref at 0x...print(r.__weakref__)# AttributeError: weakref.ReferenceType object has no attribute __weakref__逐行解析初始时__weakref__为None。创建弱引用后obj.__weakref__变成指向弱引用链表的头指针实际上是weakref对象。5.3 循环引用解决使用弱引用classNode:def__init__(self,value):self.valuevalue self.parentNoneself.children[]defadd_child(self,child):self.children.append(child)child.parentweakref.ref(self)# 使用弱引用避免循环importweakref rootNode(1)childNode(2)root.add_child(child)# 现在 root 引用 childchild 弱引用 root不会形成循环delroot# child 的 parent 弱引用变为 Nonechild 可以被正常回收逐行解析在add_child中将child.parent设置为weakref.ref(self)而不是直接self。这样当root被删除后child.parent()返回None不会阻止root被回收。避免了双向强引用造成的循环引用。5.4 回调函数对象销毁时执行操作importweakrefclassResource:def__init__(self,name):self.namenamedefcleanup(ref):print(fResource{ref}is being destroyed)rResource(my_resource)wrweakref.ref(r,cleanup)# 注册回调delr# 触发回调逐行解析weakref.ref(obj, callback)在obj被销毁时会调用callback并将弱引用对象本身作为参数传入。可用于清理外部资源或日志记录。5.5 使用WeakValueDictionary实现缓存importweakrefclassExpensiveObject:def__init__(self,id):self.idid_cacheweakref.WeakValueDictionary()defget_object(id):ifidnotin_cache:objExpensiveObject(id)_cache[id]objreturn_cache[id]obj1get_object(1)obj2get_object(1)print(obj1isobj2)# Truedelobj1# 当所有强引用消失后缓存中的条目会自动删除逐行解析WeakValueDictionary存储的值是弱引用当值对象被回收时条目会自动消失。适用于缓存场景避免缓存对象阻止垃圾回收。6. 注意事项与陷阱__slots__与弱引用如果使用__slots__且需要弱引用必须显式包含__weakref__。不可变类型int,str,tuple等内置不可变类型通常不支持弱引用因为它们没有__weakref__槽位。但int在某些 Python 实现中可能支持官方不保证。性能开销每个支持弱引用的实例都多了一个指针通常 8 字节对于大量实例可以考虑关闭弱引用通过__slots__排除__weakref__以节省内存。线程安全弱引用操作如创建、销毁涉及链表的修改CPython 内部使用全局解释器锁GIL保护因此是线程安全的。回调中的注意事项回调函数会在对象销毁时被调用此时对象已经处于半销毁状态不应再尝试访问该对象会引发ReferenceError或None。回调中应避免可能触发其他弱引用的操作。7. 与其他相关概念的区别概念说明与__weakref__的关系weakref.ref弱引用对象通过__weakref__链表与目标对象关联WeakKeyDictionary键为弱引用的字典内部使用弱引用依赖键对象的__weakref__WeakValueDictionary值为弱引用的字典内部使用弱引用依赖值对象的__weakref__weakref.proxy弱引用代理与弱引用类似但访问失败时抛出ReferenceError__slots__限制实例属性必须显式包含__weakref__才能支持弱引用__del__析构方法注意与弱引用回调的交互顺序避免复杂逻辑8. 总结特性说明角色实例内部字段用于存储指向该实例的弱引用链表是否自动创建默认是除非被__slots__排除访问方式obj.__weakref__通常不需要直接使用主要用途支持weakref模块实现弱引用、缓存、避免循环引用与__slots__的关系使用__slots__时若需弱引用必须包含__weakref__底层CPython 的tp_weaklistoffset偏移量指向PyWeakReference链表典型场景缓存、观察者模式、循环引用打破、资源跟踪最佳实践大量实例且不需要弱引用时可通过__slots__排除__weakref__节省内存需要弱引用时确保类支持它掌握__weakref__有助于深入理解 Python 的弱引用机制和内存管理。通过合理使用弱引用可以避免内存泄漏构建更健壮的应用。希望本文能帮助你全面掌握这一特殊属性。如果在学习过程中遇到问题欢迎在评论区留言讨论!

更多文章