记忆元类。创建缓存的简单方法
项目描述
一种快速的方法,使Python类自动根据它们被实例化的参数(即它们的__init__的参数)记忆化(即缓存)它们的实例。
这是一种避免反复创建昂贵对象并确保具有自然‘身份’的对象只创建一次的简单方法。如果您想要更复杂,mementos实现了多例模式。
用法
假设您有一个名为Thing的类,该类需要昂贵的计算来创建,或者应该只创建一次。简单易行
from mementos import mementos class Thing(mementos): def __init__(self, name): self.name = name ...
然后Thing对象将被记忆化
t1 = Thing("one") t2 = Thing("one") assert t1 is t2 # same instantiation args => same object
内部原理
当您定义一个类class Thing(mementos)时,它看起来像您正在继承mementos类。实际上不是。 mementos是一个元类,而不是超类。完整的表达式相当于class Thing(with_metaclass(MementoMetaclass, object)),其中with_metaclass和MementoMetaclass也由mementos模块提供。
元类不是普通的超类;相反,它们定义了类是如何被构建的。实际上,它们定义了大多数类都不必定义的神秘 __new__ 方法。在这种情况下,mementos 实际上是在说,“嘿,在创建另一个对象之前,先在缓存中查找这个对象。”
如果你愿意,你可以使用带有完整 with_metaclass 规范的较长调用,但这并不是必需的,除非你定义自己的记忆化函数。更多内容将在下面介绍。
Python 2 与 Python 3
Python 2 和 3 在指定元类方面有不同的形式。在 Python 2 中
from mementos import MementoMetaclass class Thing(object): __metaclass__ = MementoMetaclass # now I'm memoized! ...
而 Python 3 使用
class Thing3(object, metaclass=MementoMetaclass): ...
mementos 支持这两种方式。但是 Python 2 和 Python 3 不互相识别对方的元类指定语法,所以一个版本的简单代码甚至无法在另一个版本中编译。上面显示的 with_metaclass() 函数是实现跨版本兼容性的方法。它与 six 跨版本兼容模块中的功能非常相似。
小心调用签名
MementoMetaclass 在调用签名上缓存,这在 Python 中可能会有很大的变化,即使是逻辑上相同的调用。这尤其适用于使用了关键字参数的情况。例如,def func(a, b=2): pass 可以被调用为 func(1)、func(1, 2)、func(a=1)、func(1, b=2) 或 func(a=2, b=2)。所有这些都解析为相同的逻辑调用——这仅仅是两个参数的情况!如果有多于一个的关键字参数,它们可以被任意排序,从而创建许多逻辑上相同的排列。
因此,如果你使用一个逻辑上相同的调用再次实例化一个对象,但使用不同的调用结构/签名,对象不会只被创建和缓存一次——它将被创建和缓存多次。
o1 = Thing("lovely") o2 = Thing(name="lovely") assert o1 is not o2 # because the call signature is different
这可能会降低性能,并且如果依赖 mementos 创建单个对象,还可能产生错误。所以不要这么做。使用一致的调用风格,就不会有问题。
在大多数情况下,这不是一个问题,因为对象通常使用有限的参数进行实例化,你可以确保它们使用平行的调用签名进行实例化。由于这99%的时间都能工作,并且实现简单,所以这种不完美的代价是值得的。
部分签名
如果你想只使用初始化时间调用签名的一部分(即 __init__ 的参数)来定义对象的身份/缓存键,有两种方法。一种方法是使用 MementoMetaclass 并设计没有冗余属性的 __init__,然后创建一个或多个辅助方法来添加/设置有用的但非必要的数据。例如:
class OtherThing(with_metaclass(MementoMetaclass, object)): def __init__(self, name): self.name = name self.color = None # unset for now self.weight = None def set(self, color=None, weight=None): self.color = color or self.color self.weight = weight or self.weight return self ot1 = OtherThing("one").set(color='blue') ot2 = OtherThing("one").set(weight='light') assert ot1 is ot2 assert ot1.color == ot2.color == 'blue' assert ot1.weight == ot2.weight == 'light'
或者你可以定义自己的记忆化元类,使用下面描述的工厂函数。
访问工厂
mementos 的第一版定义了一个单个的元类。后来它被重新实现为一个参数化的元元类。酷吧?这意味着它定义了一个函数,memento_factory(),它接受一个元类名称和一个定义如何构建缓存键的函数作为参数,并返回一个相应的元类。MementoMetaclass 是模块预先定义的唯一元类,但很容易定义自己的记忆化元类。
from mementos import memento_factory, with_metaclass IdTracker = memento_factory('IdTracker', lambda cls, args, kwargs: (cls, id(args[0])) ) class MyTracker(with_metaclass(IdTracker, object)): ... # object identity is the object id of first argument to __init__ # (and there must be one, else the args[0] reference => IndexError)
memento_factory() 的第一个参数是要定义的元类的名称。第二个参数是一个可调用的对象(例如 lambda 表达式或函数对象),它接受三个参数:一个类对象、一个参数 list 和一个关键字参数 dict。注意,没有 * 或 ** 魔法——传递给键函数的参数已经被解析为基本数据结构。
可调用函数必须返回一个全局唯一的、可哈希的对象键。这个键将被存储在 _memento_cache 中,它是一个简单的 dict。
当使用各种参数作为缓存键/对象标识符时,您可以使用一个包含所需键的类和参数的 tuple。这也有助于调试,如果您需要直接检查 _memento_cache 缓存的话。但在像上面的 IdTracker 这样的情况下,您不需要保留额外的信息。原始 id(args[0]) 整数值就足够了,也可以使用构造的字符串或其他不可变、可哈希的值。
在参数非常灵活或涉及灵活数据类型的情况下,可以使用如 SuperHash 提供的高性能哈希函数。例如:
from superhash import superhash SuperHashMeta = memento_factory('SuperHashMeta', lambda cls, args, kwargs: (cls, superhash(args)) )
对于必须将多个调用变体最终解析为唯一规范签名的 1% 边缘情况,这可以基于特定的参数进行定制。或者在 Python 2.7 和 3.x 中,可以使用 inspect 模块的 getcallargs() 函数创建一个通用的“调用指纹”,用作键。(请参阅测试示例。)
注意
有关扩展变更日志,请参阅 CHANGES.rst。
mementos 与 memento 不同,后者执行完全不同的功能。
mementos 最初是由 Valentino Volonghi 在 ActiveState 菜谱 中提出的。虽然当前实现与该菜谱相当不同,范围也更广,但该菜谱的可用性使得这个模块以及依赖它的模块列表得以增长。这正是开源进化的全部意义。感谢,Valentino!
可以安全地同时缓存多个类。它们都将存储在同一个缓存中,但它们的类是缓存键的一部分,因此值是不同的。
该实现本身不是线程安全的。如果您处于多线程环境中,请考虑在对象实例化时使用锁。
使用 pytest、pytest-cov、coverage 和 tox 进行自动化多版本测试。使用 Travis-CI 进行持续集成测试。使用 pyroma 进行打包代码审查。
作者 Jonathan Eunice 或 @jeunice 欢迎您的评论和建议。
安装
要安装或升级到最新版本
pip install -U mementos
您可能需要在这些命令前加上 sudo 以授权安装。在没有超级用户权限的环境中,您可能想使用 pip 的 --user 选项,只为单个用户安装,而不是全局安装。根据您的系统配置,您可能还需要使用单独的 pip2 和 pip3 程序分别安装 Python 2 和 3。作为 pip 与您想运行的 Python 解释器之间关系的后备方案,您可以在特定的 Python 可执行文件下调用 pip 作为模块。
python3.6 -m pip install -U mementos
测试
要运行模块测试,请使用以下命令之一
tox # normal run - speed optimized tox -e py27 # run for a specific version only (e.g. py27, py34) tox -c toxcov.ini # run full coverage tests
项目详情
下载文件
下载适用于您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。
源分布
构建分布
mementos-1.3.1.zip的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | e3574b3d16a1b3cbfd03e049701bedcfcff131ea5f93904563672221109f53e7 |
|
MD5 | 46e7bb3db4ffbbbefabd6023a8052e49 |
|
BLAKE2b-256 | 480c1287a95aa809b618ca2961509bb77d0e3e8ef984a99b52f72f1e76514e21 |
mementos-1.3.1-py2.py3-none-any.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | fee20b2440a06657bb942b8d935f0c3d468b5ad58b07b33b609fd92fe864bc7f |
|
MD5 | 2a570330f8421ed4b41cab6cda76f1af |
|
BLAKE2b-256 | 329f44d24c854245214c1ace565aa8269b64d4ab189f4eb9783a2ac00c98072c |