懒汉模式和池化模式 3/30 | Python 主题月
写在前面
本文正在参加「Python主题月」,详情查看活动链接
这个月是 Python 活动月,我决定尝试用 Python 来刷这 30 天的每日一题和随机一题。然后如果周末有精力,我想捣鼓捣鼓这个python-patterns
设计模式对我来说更多的是学习而不是我的个人经验总结,所以我很可能理解偏,如果有大佬见到了请及时指出,我之所以选择在掘金来写一些个人的东西是因为这里的文章质量更高,我不希望后来者看到了这些文章被误导。
惰性评估模式
怎么说呢,这里确实是用到了懒这个字眼,但是懒汉模式和这个应该要区别一下。Lazy Expression Evaluation(表达式缓评估)是我这里说的惰性评估,或者惰性求值。懒汉模式是单例的一种写法。懒汉模式是不需要去创建类的实例,就不去创建类的实例。这里的惰性评估也有懒的意味,这里是用到了再算,推迟表达式求值的时机,直到需要这个值的时候才去计算,同时避免重复求值。
下面看 Python 中代码是怎么实现这种惰性求值的。
import functools
class lazy_property:
def __init__(self, function):
self.function = function
functools.update_wrapper(self, function)
def __get__(self, obj, type_):
if obj is None:
return self
val = self.function(obj)
obj.__dict__[self.function.__name__] = val
return val
def lazy_property2(fn):
attr = "_lazy__" + fn.__name__
@property
def _lazy_property(self):
if not hasattr(self, attr):
setattr(self, attr, fn(self))
return getattr(self, attr)
return _lazy_property
class Person:
def __init__(self, name, occupation):
self.name = name
self.occupation = occupation
self.call_count2 = 0
@lazy_property
def relatives(self):
# Get all relatives, let's assume that it costs much time.
relatives = "Many relatives."
return relatives
@lazy_property2
def parents(self):
self.call_count2 += 1
return "Father and mother"
def main():
"""
>>> Jhon = Person('Jhon', 'Coder')
>>> Jhon.name
'Jhon'
>>> Jhon.occupation
'Coder'
# Before we access `relatives`
>>> sorted(Jhon.__dict__.items())
[('call_count2', 0), ('name', 'Jhon'), ('occupation', 'Coder')]
>>> Jhon.relatives
'Many relatives.'
# After we've accessed `relatives`
>>> sorted(Jhon.__dict__.items())
[('call_count2', 0), ..., ('relatives', 'Many relatives.')]
>>> Jhon.parents
'Father and mother'
>>> sorted(Jhon.__dict__.items())
[('_lazy__parents', 'Father and mother'), ('call_count2', 1), ..., ('relatives', 'Many relatives.')]
>>> Jhon.parents
'Father and mother'
>>> Jhon.call_count2
1
"""
if __name__ == "__main__":
import doctest
doctest.testmod(optionflags=doctest.ELLIPSIS)
复制代码
应该不用解释了,代码里写的很明白。
池化模式
池化模式存储一组初始化的对象,保持随时可以使用。和前面那种懒是不一样的懒法,这里是先一次性初始化好,后面要用直接用,今天介绍的惰性求值和池化两个模式的共同点是避免重复初始化。
当创建一个对象的成本很高(而且它们被频繁地创建),但一次只使用几个时,就会使用这种模式。通过一个池子,我们可以通过缓存来管理我们目前拥有的这些实例。如果池子里有一个现成的对象,我们就可以跳过昂贵的创建过程。池允许“检出”一个不活动的对象,然后将其回收。如果没有可用的对象,池子就会创建一个可用的对象,无需等待。
下面看代码
class ObjectPool:
def __init__(self, queue, auto_get=False):
self._queue = queue
self.item = self._queue.get() if auto_get else None
def __enter__(self):
if self.item is None:
self.item = self._queue.get()
return self.item
def __exit__(self, Type, value, traceback):
if self.item is not None:
self._queue.put(self.item)
self.item = None
def __del__(self):
if self.item is not None:
self._queue.put(self.item)
self.item = None
def main():
"""
>>> import queue
>>> def test_object(queue):
... pool = ObjectPool(queue, True)
... print('Inside func: {}'.format(pool.item))
>>> sample_queue = queue.Queue()
>>> sample_queue.put('yam')
>>> with ObjectPool(sample_queue) as obj:
... print('Inside with: {}'.format(obj))
Inside with: yam
>>> print('Outside with: {}'.format(sample_queue.get()))
Outside with: yam
>>> sample_queue.put('sam')
>>> test_object(sample_queue)
Inside func: sam
>>> print('Outside func: {}'.format(sample_queue.get()))
Outside func: sam
if not sample_queue.empty():
print(sample_queue.get())
"""
if __name__ == "__main__":
import doctest
doctest.testmod()
复制代码
这个模式国内翻译成对象池模式的比较多,但是 Python 里面其实哪哪都是对象,感觉直接叫池模式或者池化模式(不要和神经网络那里的池化搞混)应该也没问题?
在这个例子中,queue.Queue
被用来创建池(被包装成一个自定义的 ObjectPool
对象,与 with
语句一起使用),它被填充了字符串。我们可以看到,第一个放入 yam
的字符串对象被 with
语句使用了。但是因为它在之后被释放回池子里,所以它被明确地调用 sample_queue.get()
而重新使用。同样的事情也发生在 sam
中,当函数中创建的 ObjectPool
被删除(通过GC),对象被回收。
小结
惰性求值模式先有需求再满足。池化模式不管需求直接把需要复杂初始化的对象创建好,后面要用直接就有,少了立马补,多了再回收。两者都要保证没有重复初始化发生。