元编程创建缓存实例

单例模式要求一个类只能创建一个实例,缓存类和单例模式类似,前者要求一个标识符(名字)关联到一个唯一的类实例。例如Python模块logging中的getLogger函数的行为:一个名字关联到一个日志类实例,如果我们尝试用同一个名字创建多个实例,发现这些实例都是唯一的(一样的),这种行为和单例模式很像,因此,我们可以把缓存类称为名字单例模式。

下面我们来看看logging中的行为:

1
2
3
4
5
6
7
8
9
>>> import logging
>>> a = logging.getLogger('a')
>>> b = logging.getLogger('a')
>>> a is b
True
>>> c = logging.getLogger('c')
>>> a is c
False
>>>

实现依据名字缓存实例的效果,首先想到的数据结构就是字典。通过字典绑定名字和实例即可。每次创建或访问通过该字典缓存实例,而字典的键是唯一的,因此可以实现“名字单例模式”。但普通的字典在缓存类实例时会为该对象添加一个引用计数,这样,当外界不再使用该实例时,对象的引用计数依然不为0,导致它无法被垃圾回收。因此这里使用弱引用字典。即:

1
2
3
4
5
6
7
8
9
10
11
12
import weakref

_cache = weakref.WeakValueDictionary()

def getLogger(name):
c = _cache.get(name, None)
if c is None:
c = Logger(name)
_cache[name] = c
return c
else:
return c

除了使用工厂方法外,还可以使用类的方式,后者更优雅。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import weakref

class Logger:

_cache = weakref.WeakValueDictionary()

def __init__(self, name):
self.name = name

def __new__(cls, name):
if name in cls._cache:
return cls._cache[name]
else:
self = super().__new__(cls)
cls._cache[name] = self
return self

# other logger method

如果要实现更负责的缓存类实例管理,可以把WeakValueDictionary从缓存类Logger分离开来,实现单独管理缓存实例的类,达到创建实例和缓存实例的松耦合。

最后,还有一种更高级的思路,使用元编程。元类负责管理类的创建,而具体类负责功能的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import weakref

class Cached(type):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__cache = weakref.WeakValueDictionary()

# 创建具体实例
def __call__(self, name):
if name in self.__cache:
return self.__cache[name]
else:
self = super().__call__(name)
self.__cache[name] = self
return self

class Logger(metaclass=Cached):

def __init__(self, name):
self.name = name

# other logger method

可见,元类也是创建实例和缓存实例结构的方式,而且使用元类代码更简洁。

转载请包括本文地址:https://allenwind.github.io/blog/4970
更多文章请参考:https://allenwind.github.io/blog/archives/