python弱引用的应用场景,

  python弱引用的应用场景,

  弱引用存在于很多语言中,最常用于解决循环引用问题。下面这篇文章主要介绍Python中弱引用的神奇用法和原理的相关信息。通过示例代码非常详细的介绍,有需要的朋友可以参考一下。

  00-1010典型用法工作原理实施细节背景概述

  

目录

  在开始讨论弱引用之前,我们先来看看什么是弱引用?它到底是做什么的?

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

  #它占用大量资源,创建和销毁成本高昂\

  类别数据:\

  def __init__(self,key):\

  及格

  应用程序数据由一个键唯一标识,相同的数据可能被多个线程同时访问。因为数据占用了大量的系统资源,所以创建和消耗的成本非常高。我们希望数据在程序中只维护一个副本,即使被多个线程同时访问,我们也不想重复创建。

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

  导入线程

  #数据缓存

  类别缓存器:

  def __init__(self):

  self.pool={}

  self.lock=线程。锁定()

  def get(self,key):

  使用self.lock:

  data=self.pool.get(key)

  如果数据:

  返回数据

  self.pool[key]=data=Data(key)

  返回数据

  Cacher内部使用dict对象来缓存创建的数据副本,并提供get方法来获取应用程序数据。获取数据时,get方法先查找缓存字典,如果数据已经存在,就直接返回;如果数据不存在,创建一个并保存在字典中。因此,在第一次创建数据后,它进入缓存字典。如果其他线程同时访问它,它们都使用缓存中的同一个副本。

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

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

  我们可以在缓存器中维护数据的引用次数,get方法会自动累计这个计数。同时提供了一个新的remove方法来释放数据,该方法首先减少引用数,当引用数下降到零时,从缓存字段中删除数据。

  调用thread get方法获取数据,当数据用完时调用remove方法释放。Cacher相当于自己实现引用计数方法,太麻烦了!Python不是内置了垃圾收集机制吗?为什么应用需要自己实现?

  冲突的主要症结在于Cacher的缓存字典:作为中间件,它不使用数据对象,所以理论上不应该引用数据。有没有不引用就能找到目标对象的黑科技?众所周知,所有的作业都会导致参考!

  

背景

  这时,弱引用(weak ref)隆重登场了!弱引用是一种特殊的对象,它可以在没有引用的情况下关联目标对象。

  #创建一个数据

  d=数据( fasionchan.com )

  拥有

  __main__。0x1018571f0处的数据对象

  #创建对此数据的弱引用

  进口武器f

  r=weakref.ref(d)

  #调用弱引用对象来查找指向的对象。

  r()

  __main__。0x1018571f0处的数据对象

  r()是d

  真实的

  #删除临时变量D,数据对象将没有其他引用,它将被回收

  德尔迪

  #再次调用弱引用对象,发现目标数据对象不再存在(返回None)

  r()

  re>

  

  这样一来,我们只需将 Cacher 缓存字典改成保存弱引用,问题便迎刃而解!

  

import threading

  import weakref

  # 数据缓存

  class Cacher:

   def __init__(self):

   self.pool = {}

   self.lock = threading.Lock()

   def get(self, key):

   with self.lock:

   r = self.pool.get(key)

   if r:

   data = r()

   if data:

   return data

   data = Data(key)

   self.pool[key] = weakref.ref(data)

   return data

  由于缓存字典只保存 Data 对象的弱引用,因此 Cacher 不会影响 Data 对象的引用计数。当所有线程都用完数据后,引用计数就降为零因而被释放。

  实际上,用字典缓存数据对象的做法很常用,为此weakref模块还提供了两种只保存弱引用的字典对象:

  

  • weakref.WeakKeyDictionary,键只保存弱引用的映射类(一旦键不再有强引用,键值对条目将自动消失);
  • weakref.WeakValueDictionary,值只保存弱引用的映射类(一旦值不再有强引用,键值对条目将自动消失);

  因此,我们的数据缓存字典可以采用weakref.WeakValueDictionary来实现,它的接口跟普通字典完全一样。这样我们不用再自行维护弱引用对象,代码逻辑更加简洁明了:

  

import threading

  import weakref

  # 数据缓存

  class Cacher:

   def __init__(self):

   self.pool = weakref.WeakValueDictionary()

   self.lock = threading.Lock()

   def get(self, key):

   with self.lock:

   data = self.pool.get(key)

   if data:

   return data

   self.pool[key] = data = Data(key)

   return data

  weakref模块还有很多好用的工具类和工具函数,具体细节请参考官方文档,这里不再赘述。

  

  

工作原理

  那么,弱引用到底是何方神圣,为什么会有如此神奇的魔力呢?接下来,我们一起揭下它的面纱,一睹真容!

  

>>> d = Data(fasionchan.com)

  # weakref.ref 是一个内置类型对象

  >>> from weakref import ref

  >>> ref

  <class weakref>

  # 调用weakref.ref类型对象,创建了一个弱引用实例对象

  >>> r = ref(d)

  >>> r

  <weakref at 0x1008d5b80; to Data at 0x100873d60>

  经过前面章节,我们对阅读内建对象源码已经轻车熟路了,相关源码文件如下:

  

  • Include/weakrefobject.h头文件包含对象结构体和一些宏定义;
  • Objects/weakrefobject.c源文件包含弱引用类型对象及其方法定义;

  我们先扒一扒弱引用对象的字段结构,定义于Include/weakrefobject.h头文件中的第10-41行:

  

typedef struct _PyWeakReference PyWeakReference;

  /* PyWeakReference is the base struct for the Python ReferenceType, ProxyType,

   * and CallableProxyType.

   */

  #ifndef Py_LIMITED_API

  struct _PyWeakReference {

   PyObject_HEAD

   /* The object to which this is a weak reference, or Py_None if none.

   * Note that this is a stealth reference: wr_objects refcount is

   * not incremented to reflect this pointer.

   */

   PyObject *wr_object;

   /* A callable to invoke when wr_object dies, or NULL if none. */

   PyObject *wr_callback;

   /* A cache for wr_objects hash code. As usual for hashes, this is -1

   * if the hash code isnt known yet.

   */

   Py_hash_t hash;

   /* If wr_object is weakly referenced, wr_object has a doubly-linked NULL-

   * terminated list of weak references to it. These are the list pointers.

   * If wr_object goes away, wr_object is set to Py_None, and these pointers

   * have no meaning then.

   */

   PyWeakReference *wr_prev;

   PyWeakReference *wr_next;

  };

  #endif

  由此可见,PyWeakReference结构体便是弱引用对象的肉身。它是一个定长对象,除固定头部外还有5个字段:

  

  

  • wr_object,对象指针,指向被引用对象,弱引用根据该字段可以找到被引用对象,但不会产生引用;
  • wr_callback,指向一个可调用对象,当被引用的对象销毁时将被调用;
  • hash,缓存被引用对象的哈希值;
  • wr_prev和wr_next分别是前后向指针,用于将弱引用对象组织成双向链表;

  结合代码中的注释,我们知道:

  

  

  • 弱引用对象通过wr_object字段关联被引用的对象,如上图虚线箭头所示;
  • 一个对象可以同时被多个弱引用对象关联,图中的Data实例对象被两个弱引用对象关联;
  • 所有关联同一个对象的弱引用,被组织成一个双向链表,链表头保存在被引用对象中,如上图实线箭头所示;
  • 当一个对象被销毁后,Python 将遍历它的弱引用链表,逐一处理:
    • 将 wr_object 字段设为 None ,弱引用对象再被调用将返回 None ,调用者便知道对象已经被销毁了;
    • 执行回调函数wr_callback(如有);

  由此可见,弱引用的工作原理其实就是设计模式中的观察者模式(Observer)。当对象被销毁,它的所有弱引用对象都得到通知,并被妥善处理。

  

  

实现细节

  掌握弱引用的基本原理,足以让我们将其用好。如果您对源码感兴趣,还可以再深入研究它的一些实现细节。

  前面我们提到,对同一对象的所有弱引用,被组织成一个双向链表,链表头保存在对象中。由于能够创建弱引用的对象类型是多种多样的,很难由一个固定的结构体来表示。因此,Python 在类型对象中提供一个字段 tp_weaklistoffset ,记录弱引用链表头指针在实例对象中的偏移量。

  

  由此一来,对于任意对象 o ,我们只需通过 ob_type 字段找到它的类型对象 t ,再根据 t 中的 tp_weaklistoffset 字段即可找到对象 o 的弱引用链表头。

  Python 在Include/objimpl.h头文件中提供了两个宏定义:

  

/* Test if a type supports weak references */

  #define PyType_SUPPORTS_WEAKREFS(t) ((t)->tp_weaklistoffset > 0)

  #define PyObject_GET_WEAKREFS_LISTPTR(o) \

   ((PyObject **) (((char *) (o)) + Py_TYPE(o)->tp_weaklistoffset))

  

  • PyType_SUPPORTS_WEAKREFS 用于判断类型对象是否支持弱引用,仅当 tp_weaklistoffset 大于零才支持弱引用,内置对象 list 等都不支持弱引用;
  • PyObject_GET_WEAKREFS_LISTPTR 用于取出一个对象的弱引用链表头,它先通过 Py_TYPE 宏找到类型对象 t ,再找通过 tp_weaklistoffset 字段确定偏移量,最后与对象地址相加即可得到链表头字段的地址;

  我们创建弱引用时,需要调用弱引用类型对象weakref并将被引用对象 d 作为参数传进去。弱引用类型对象weakref是所有弱引用实例对象的类型,是一个全局唯一的类型对象,定义在Objects/weakrefobject.c中,即:_PyWeakref_RefType(第 350 行)。

  

  根据对象模型中学到的知识,Python调用一个对象时,执行的是其类型对象中的tp_call函数。因此,调用弱引用类型对象weakref时,执行的是weakref的类型对象,也就是type的tp_call函数。tp_call 函数则回过头来调用 weakref 的 tp_new 和 tp_init 函数,其中 tp_new 为实例对象分配内存,而 tp_init 则负责初始化实例对象。

  回到Objects/weakrefobject.c源文件,可以看到 PyWeakref_RefType 的 tp_new 字段被初始化成*weakref___new_* (第276行)。该函数的主要处理逻辑如下:

  

  • 解析参数,得到被引用的对象(第 282 行);
  • 调用PyType_SUPPORTS_WEAKREFS宏判断被引用的对象是否支持弱引用,不支持就抛异常(第 286 行);
  • 调用GET_WEAKREFS_LISTPTR行取出对象的弱引用链表头字段,为方便插入返回的是一个二级指针(第 294 行);
  • 调用 get_basic_refs 取出链表最前那个 callback 为空基础弱引用对象(如有,第 295 行);
  • 如果callback为空,而且对象存在callback为空的基础弱引用,则复用该实例直接将其返回(第 296 行);
  • 如果不能复用,调用 tp_alloc 函数分配内存、完成字段初始化,并插到对象的弱引用链表(第 309 行);
    • 如果 callback 为空,直接将其插入到链表最前面,方便后续复用(见第 4 点);
    • 如果 callback 非空,将其插到基础弱引用对象(如有)之后,保证基础弱引用位于链表头,方便获取;

  当一个对象被回收后,tp_dealloc 函数将调用PyObject_ClearWeakRefs函数对它的弱引用进行清理。该函数取出对象的弱引用链表,然后逐个遍历,清理wr_object字段并执行wr_callback回调函数(如有)。具体细节不再展开,有兴趣的话可以自行查阅Objects/weakrefobject.c中的源码,位于880行。

  好了,经过本节学习,我们彻底掌握了弱引用相关知识。弱引用可以在不产生引用计数的前提下,对目标对象进行管理,常用于框架和中间件中。弱引用看起来很神奇,其实设计原理是非常简单的观察者模式。弱引用对象创建后便插到一个由目标对象维护的链表中,观察(订阅)对象的销毁事件。

  

  

总结

  到此这篇关于Python中弱引用的神奇用法与原理的文章就介绍到这了,更多相关Python弱引用用法内容请搜索盛行IT软件开发工作室以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT软件开发工作室!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: