class CachedProperty(object):
_cache = {}
def __call__(self, fn):
@property
@functools.wraps(fn)
def wrapped(self):
k = '%s:%s' % (id(self), fn.__name__)
v = CachedProperty._cache.get(k)
if v is None:
v = fn(self)
CachedProperty._cache[k] = v
return v
return wrapped
cached_property = CachedProperty()
使用的时候可以直接替换掉内置的 property 来缓存动态属性:
class DB(object):
def __init__(self, ...):
....
@cached_property
def conn(self):
return create_connection(...)
db = DB()
db.conn # 创建连接并缓存
还有个更复杂但非常实用的例子,Pipline,把函数封装成支持管道操作的运算过程:
class Pipe(object):
def __init__(self, fn):
self.fn = fn
def __ror__(self, other):
return self.fn(other)
def __call__(self, *args, **kwargs):
op = Pipe(lambda x: self.fn(x, *args, **kwargs))
return op
用一句话描述是,
Python 的
__call__
方法可以让类的实例具有类似于函数的行为,通常用于定义和初始化一些带有上下文的函数。既然说是妙处,那不得不提及 Python 世界中的那几个经典实现了。
一个例子来源于 bottle 框架源码的
cached_property
(被我改动了一些细节,但用法基本是一样的),为了在逻辑上构成一个封闭的整体,我们把一个实例当作函数来使用:使用的时候可以直接替换掉内置的
property
来缓存动态属性:还有个更复杂但非常实用的例子,Pipline,把函数封装成支持管道操作的运算过程:
可以像这样调用:
可以看出,类
Pipe
被当作一个装饰器使用,所以sort
函数的原始定义被传递给Pipe.__init__
,构造出一个 Pipe 实例,所以被装饰过的sort
函数,也就是我们后面使用的那个,实际上是一个Pipe
类的实例,只是因为它有__call__
方法,所以可以作为函数来使用。这种用法在写一些 ORM 框架以及有大量细粒度行为的库时有奇效。