元编程创建缓存实例
单例模式要求一个类只能创建一个实例,缓存类和单例模式类似,前者要求一个标识符(名字)关联到一个唯一的类实例。例如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
|
如果要实现更负责的缓存类实例管理,可以把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
|
可见,元类也是创建实例和缓存实例结构的方式,而且使用元类代码更简洁。
转载请包括本文地址:https://allenwind.github.io/blog/4970
更多文章请参考:https://allenwind.github.io/blog/archives/