Kids缓存库。
项目描述
kids.cache 是一个Python库,提供缓存装饰器。它是‘Kids’(为了保持简单)库的一部分。它不依赖于任何Python库。
其主要关注点是提供一种非常简单的默认使用方案,同时不忘在需要时提供完整的功能。
成熟度
这段代码大约有100行Python代码,并且有100%的测试覆盖率。
然而,目前它仍然被认为是beta阶段。
兼容性
它很小很简单,应该可以在任何地方工作。
更详细地说:当前的代码足够简单,它使用了Python的公共子集,兼容任何平台上的Python 2.7和Python >= 3……而且没有任何特定的修改。
即使如此,您可能会很高兴地知道,这段代码已经针对Python 2.7、3.4、3.5、3.6在Linux和Windows平台上进行了每个提交的兼容性测试。
特性
通过一个简单的调用 @cache,就可以消除大部分隐藏的复杂性。
在您可以将装饰器(函数、方法、属性、类等)粘附的所有地方都可以正常工作。
支持在 @property、@classmethod、@staticmethod 等常用装饰器之前或之后调用。
使用 @cache 可以实现几个设计模式
memoization 当用于带参数的函数时。
lazy evaluation 当放置在属性上时。
singleton 模式,当放置在类上时。
提供全面的自定义
缓存清除或缓存统计功能。
支持来自cachetools包的任何缓存存储机制。
支持自定义键函数,允许
支持您的外来不可哈希对象
微调哪些函数调用可以被认为是相同的
在对象中手动选择函数依赖(对于方法)
基本用法
函数
此缓存装饰器非常简单易用
>>> from kids.cache import cache >>> @cache ... def answer_to_everything(): ... print("many insightfull calculation") ... return 42
然后,函数answer_to_everything仅在第一次调用时执行计算,并保存结果,在后续调用时直接返回它
>>> answer_to_everything() many insightfull calculation 42 >>> answer_to_everything() 42
函数体不再执行,而是使用缓存值。
它可以与参数一起工作
>>> @cache ... def mysum(*args): ... print("calculating...") ... return sum(args) >>> mysum(2, 2, 3) calculating... 7 >>> mysum(1, 1, 1, 1) calculating... 4 >>> mysum(2, 2, 3) 7 >>> mysum(1, 1, 1, 1) 4
请注意,默认情况下,对象是无类型的,因此
>>> mysum(1.0, 1, 1, 1) 4
触发了缓存,尽管第一个参数是一个浮点数而不是整数。
方法
使用方法
>>> class MyObject(object): ... def __init__(self, a, b): ... self.a, self.b = a, b ... ... @cache ... def total(self): ... print("calculating...") ... return self.a + self.b >>> xx = MyObject(2, 3) >>> xx.total() calculating... 5 >>> xx.total() 5
缓存不会在实例之间共享
>>> yy = MyObject(2, 3) >>> yy.total() calculating... 5
当然,如果您更改实例的内部值,缓存方法将不会检测到这一点
>>> xx.a = 5 >>> xx.total() 5
查看高级用法以了解如何更改这些行为的一些方法。
属性
您可以使用cache装饰器与属性一起使用,并为具有延迟评估属性的属性提供了一种很好的方式
>>> class WithProperty(MyObject): ... ... @property ... @cache ... def total(self): ... print("evaluating...") ... return self.a + self.b >>> xx = WithProperty(1, 1) >>> xx.total evaluating... 2 >>> xx.total 2
您可以在@cache装饰器之前或之后使用@property装饰器
>>> class WithProperty(MyObject): ... ... @cache ... @property ... def total(self): ... print("evaluating...") ... return self.a + self.b >>> xx = WithProperty(2, 2) >>> xx.total evaluating... 4 >>> xx.total 4
类方法
您可以使用cache装饰器与类方法一起使用,并为实例之间共享缓存提供了一种很好的方式
>>> class WithClassMethod(MyObject): ... ... a = 2 ... b = 3 ... ... @classmethod ... @cache ... def total(cls): ... print("evaluating...") ... return cls.a + cls.b >>> WithClassMethod.total() evaluating... 5 >>> WithClassMethod.total() 5
您可以在@cache装饰器之前或之后使用@property装饰器
>>> class WithClassMethod(MyObject): ... ... a = 1 ... b = 6 ... ... @cache ... @classmethod ... def total(cls): ... print("evaluating...") ... return cls.a + cls.b >>> WithClassMethod.total() evaluating... 7 >>> WithClassMethod.total() 7
静态方法
您可以使用cache装饰器与静态方法一起使用
>>> class WithStaticMethod(MyObject): ... ... @staticmethod ... @cache ... def total(a, b): ... print("evaluating...") ... return a + b >>> WithStaticMethod.total(1, 3) evaluating... 4 >>> WithStaticMethod.total(1, 3) 4
您可以在@cache装饰器之前或之后使用@property装饰器
>>> class WithStaticMethod(MyObject): ... ... @cache ... @staticmethod ... def total(a, b): ... print("evaluating...") ... return a + b >>> WithStaticMethod.total(2, 6) evaluating... 8 >>> WithStaticMethod.total(2, 6) 8
类
使用cache与类将允许围绕单例的概念进行变化。单例在内存中共享相同的id,因此这显示了经典的非单例行为
>>> a, b = object(), object() >>> id(a) == id(b) False
基于工厂的单例
您可以使用cache装饰器与类一起使用,实际上实现了创建单例的工厂模式
>>> @cache ... class MySingleton(MyObject): ... def __new__(cls): ... print("instanciating...") ... return MyObject.__new__(cls) ... def __init__(self): ... print("initializing...") >>> a, b = MySingleton(), MySingleton() instanciating... initializing... >>> id(a) == id(b) True
请注意,这两个实例都是相同的对象,因此它只被实例化和初始化了一次。
但请注意:这不再是类了
>>> MySingleton <function MySingleton at ...>
基于实例的单例
略有不同,可以通过缓存__new__来实现类单例模式
>>> class MySingleton(MyObject): ... @cache ... def __new__(cls): ... print("instanciating...") ... return MyObject.__new__(cls) ... def __init__(self): ... print("initializing...") >>> a, b = MySingleton(), MySingleton() instanciating... initializing... initializing... >>> id(a) == id(b) True
请注意,这两个实例都是相同的对象,因此它只被实例化了一次。但__init__被调用了两次。这有时是完全可以接受的,但您可能想避免这种情况。
所以,如果您不想这样做,您应该也缓存__init__方法
>>> class MySingleton(MyObject): ... @cache ... def __new__(cls): ... print("instanciating...") ... return MyObject.__new__(cls) ... @cache ... def __init__(self): ... print("initializing...") >>> a, b = MySingleton(), MySingleton() instanciating... initializing... >>> id(a) == id(b) True
当然,在这两种情况下,您都会保留完整的对象不受影响
>>> MySingleton <class 'MySingleton'>
带参数的单例
实际上,只有在您以相同的参数连续调用它们时,这些才是单例。
更准确地说,当它们的实例化参数相同时,您可以共享您的类
>>> @cache ... class MySingleton(MyObject): ... def __init__(self, a): ... self.a = a ... print("evaluating...") >>> a, b = MySingleton(1), MySingleton(2) evaluating... evaluating... >>> id(a) == id(b) False
但是
>>> c = MySingleton(1) >>> id(a) == id(c) True
如果您想要一个即使在连续调用中不同也能给出相同实例的单例,您应该查看高级用法部分和key参数。
高级用法
大多数高级用法意味着要以参数调用@cache装饰器。请注意
>>> @cache ... def mysum1(*args): ... print("calculating...") ... return sum(args)
或者
>>> @cache() ... def mysum2(*args): ... print("calculating...") ... return sum(args)
是等价的
>>> mysum1(1,1) calculating... 2 >>> mysum1(1,1) 2 >>> mysum2(1,1) calculating... 2 >>> mysum2(1,1) 2
提供键函数
提供键函数可以非常强大,并允许您精确控制何时重新计算缓存。
哈希函数将接收与主函数调用完全相同的参数。它必须返回一个可哈希的结构(tuples、int、string等的组合…避免列表、字典和集合)。这将唯一标识结果。
例如您可以
>>> class WithKey(MyObject): ... @cache(key=lambda s: (id(s), s.a, s.b)) ... def total(self): ... print("calculating...") ... return self.a + self.b >>> xx = WithKey(2, 3) >>> xx.total() calculating... 5 >>> xx.total() 5
它应该检测实例给定值的更改
>>> xx.a = 5 >>> xx.total() calculating... 8
而不必担心其他值更改时的重新计算
>>> xx.c = 7 >>> xx.total() 8
但它应该仍然在实例之间做出区别
>>> yy = WithKey(2, 3) >>> yy.total() calculating... 5
这个最后的例子很重要,因为您可能希望所有实例之间共享缓存。您可以通过在键函数中避免返回id(s)轻松实现这一点。
类型键函数
您可以要求typed参数不被视为相同
>>> @cache(typed=True) ... def mysum(*args): ... print("calculating...") ... return sum(args) >>> mysum(1, 1) calculating... 2 >>> mysum(1.0, 1) calculating... 2.0
默认键函数
如果没有提供默认键函数,将尝试创建一个list和dict,set也将可作为键,尽管这些不是可哈希的。
键函数的名称为hippie_hashing,这是键参数的默认值
>>> from kids.cache import hippie_hashing >>> @cache(key=hippie_hashing) ... def mylength(obj): ... return len(obj)
这允许您使用函数与列表、字典或这些的组合
>>> mylength([set([3]), 2, {1: 2}]) 3
即使您的对象也可以用作键,只要它们是可哈希的
>>> class MyObj(object): ## object subclasses have a default hash ... length = 5 ... def __len__(self, ): ... print('calculating...') ... return self.length >>> myobj = MyObj() >>> mylength(myobj) calculating... 5 >>> mylength(myobj) 5
请放心,哈希冲突(它们会发生!)不会生成缓存冲突
>>> class MyCollidingHashObj(MyObj): ... def __init__(self, length): ... self.length = length ... def __hash__(self): ... return 1 >>> hash_collide1 = MyCollidingHashObj(6) >>> hash_collide2 = MyCollidingHashObj(7) >>> mylength(hash_collide1) calculating... 6 >>> mylength(hash_collide2) calculating... 7
但为了性能考虑,尽量避免它们!并且您可能应该意识到,如果您的对象相等,则将存在缓存冲突(但在这个时候,这可能正是您想要的,哈哈?)
>>> class MyEqCollidingHashObj(MyCollidingHashObj): ... def __eq__(self, value): ... return True ... def __hash__(self): ... return 1 >>> eq_and_hash_collide1 = MyEqCollidingHashObj(8) >>> eq_and_hash_collide2 = MyEqCollidingHashObj(9) >>> mylength(eq_and_hash_collide1) calculating... 8 >>> mylength(eq_and_hash_collide2) 8
哎呀。这可能不是在这个例子中预期的,但您真的必须努力工作才能实现这一点。大多数时候,您可能会发现这很方便,并且会利用它。这有点像对象责任的key机制的扩展。
当然,hippie_hashing在特殊不可哈希的对象上会失败
>>> class Unhashable(object): ... def __hash__(self): ... raise ValueError("unhashable!") >>> hippie_hashing(Unhashable()) ## doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: <Unhashable ...> can not be hashed. Try providing a custom key function.
如果您不是嬉皮士,您应该考虑使用strict=True,并将使用更有限的方法来从您的参数中生成键
>>> @cache(strict=True) ... def mylength(obj): ... return len(obj) >>> mylength("hello") 5
但那时,如果它与字典、列表或集合参数一起失败,请不要感到惊讶
>>> mylength([set([3]), 2, {1: 2}]) Traceback (most recent call last): ... TypeError: unhashable type: 'list'
您可以将typed=True与strict=True结合使用
>>> @cache(strict=True, typed=True) ... def mysum(*args): ... print("calculating...") ... return sum(args) >>> mysum(1, 1) calculating... 2 >>> mysum(1.0, 1) calculating... 2.0
一个好的键函数可以
设置一些缓存超时(但您应该查看缓存存储部分以限制缓存的大小)
精细选择与该方法相关的参数,以避免在非必要的情况下重新评估函数。
允许您缓存具有非常特殊参数的可调用对象,这些参数无法正确哈希。
清理缓存
kids.cache使用了Python 3实现中的一些lru_cache思想,每个缓存的函数都收到一个cache_clear方法
>>> @cache ... def mysum(*args): ... print("calculate...") ... return sum(args) >>> mysum(1,1) calculate... 2 >>> mysum(1,1) 2
通过调用cache_clear方法,我们可以清除所有之前的缓存值
>>> mysum.cache_clear() >>> mysum(1,1) calculate... 2
缓存统计
kids.cache使用了Python 3实现中的一些lru_cache思想,每个缓存的函数都收到一个cache_info方法
>>> @cache ... def mysum(*args): ... print("calculate...") ... return sum(args) >>> mysum(1,1) calculate... 2 >>> mysum(1,1) 2 >>> mysum.cache_info() CacheInfo(type='dict', hits=1, misses=1, maxsize=None, currsize=1)
缓存存储
kids.cache可以使用任何类似于字典的结构作为缓存存储。这意味着您可以提供一些更聪明的缓存存储。例如,您可以使用底层的cachetools缓存来管理缓存存储。
请记住,默认的缓存存储是…一个字典!如果您的程序将运行很长时间,并且您缓存了在整个运行期间将不同的函数调用,那么这不是一个好主意:缓存存储将随着每个新调用的到来而增长,使您的进程的内存使用量增长……也许超出了界限。
在这种情况下,您必须考虑使用受管理的缓存存储,它将清理和删除旧的未使用缓存条目。在cachetools和kids.cache中提供了许多缓存存储,并且kids.cache支持它们。
如果您需要来自 cachetools 的任何缓存存储,您可以提供它
>>> from cachetools import LRUCache
LRU 代表最近最少使用…
>>> @cache(use=LRUCache(maxsize=2)) ... def mysum(*args): ... print("calculate...") ... return sum(args) >>> mysum(1, 1) calculate... 2 >>> mysum(1, 2) calculate... 3 >>> mysum(1, 3) calculate... 4
我们已经超过缓存内存,最近最少使用的已丢弃
>>> mysum(1, 1) calculate... 2
但我们仍然有一个在内存中
>>> mysum(1, 3) 4
贡献
任何建议或问题都欢迎。非常欢迎推送请求,请查看指南。
推送请求指南
您可以发送任何代码。我会查看它,并自己将其集成到代码库中,并保留您为作者。这个过程可能需要时间,如果您遵循以下指南,则所需时间会更少。
使用 PEP8 或 pylint 检查您的代码。尽量保持每行宽度为 80 列。
按最小关注点分别提交。
每个提交应通过测试(以便轻松的二分法)
每个功能/错误修复提交应包含代码、测试和文档。
优先处理带有排版或代码外观更改的较小提交。这些应该在提交摘要中使用 !minor 标记。
提交消息应遵循 gitchangelog 规则(检查 git 日志以获取示例)
如果提交修复了问题或完成了功能的实现,请在摘要中说明。
如果您对这里未回答的指南有任何疑问,请检查当前的 git log,您可能会找到以前提交的示例,说明如何处理您的问题。
许可证
版权 (c) 2017 Valentin Lab。
在 BSD 许可证 下授权。
变更日志
0.0.7 (2017-11-16)
修复
修复了生成的变更日志与 README.rst 之间的 ReST 不一致。 [Valentin Lab]
这阻止了 PyPI 页面正确渲染。
0.0.6 (2017-11-16)
修复
修复了由于过时的命名空间模式导致的导入时间性能问题。(修复 #9) [Valentin Lab]
0.0.4 (2015-04-27)
新增
支持在 staticmethod 装饰器之前或之后调用。 [Valentin Lab]
支持在 classmethod 装饰器之前或之后调用。 [Valentin Lab]
变更
记录了在 class 中使用时单例模式的使用。 [Valentin Lab]
0.0.3 (2015-02-24)
修复
如果两个自定义对象共享相同的哈希和类型但不是 equal,则会出现讨厌的缓存冲突。 [Valentin Lab]
事实上,这种情况确实发生了。例如,所有 object 的实例或任何子类都将继承一个使用 id 的特殊 hash 方法,但在某些版本的 python(最新版本)中,id 值被除以 16。并且,由于哈希冲突是不可避免的,因此不应导致缓存冲突。
0.0.2 (2015-02-02)
新增
将类型添加到缓存统计信息中,删除了对 cachetools 的依赖。 [Valentin Lab]
变更
默认缓存存储的 currsize 使用 len() 而不是 None。 [Valentin Lab]
这对于默认字典实现来说是合理的。
修复
错误归因于 cache_clear 和 cache_info 函数。 [Valentin Lab]
相似的 set 可能得到不同的哈希值。 [Valentin Lab]
在哈希之前未对 set 进行排序。
0.0.1 (2014-05-23)
第一次导入。 [Valentin Lab]
项目详情
下载文件
下载您平台上的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。
源代码发行版
构建版本
kids.cache-0.0.7.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 6630bead0d43ef700a8eb4729fdc09e8fbfeba7211a2563f4bd67e5659f97853 |
|
MD5 | e8c79b089133e2ddb95ee78c4b0a5028 |
|
BLAKE2b-256 | 36fe12a3ac5da6f3b5b70da5e59db908b24d0fa1797954a3210bb7146020153c |
kids.cache-0.0.7-py2.py3-none-any.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 685b30a8aea1d225d0490f73a54955213b36d794e8d332a93c1db3e46e1ef11d |
|
MD5 | 124d31ea2402ba1b33e733b22e4120d8 |
|
BLAKE2b-256 | 86d222a34ea6a4181a764ebad7097e5b25264436eeead4030b243b5ccfd95cfe |