类属性缓存装饰器(从cached-property分支而来)。
项目描述
类属性缓存装饰器(从cached-property分支而来)。
由于上游库的维护者似乎不再维护它,因此从该库分支了这个库。它可以作为完全兼容API的替代品(在代码中用property_cached代替cached_property,效果立竿见影)。如果原库的开发重新开始,这个库可能会被弃用。
以下包含稍作修改的README
为什么?
使缓存耗时或计算成本高的属性变得快速且简单。
因为我厌倦了从非Web项目复制粘贴这段代码到非Web项目中。
如何使用它
让我们定义一个具有成本高昂的属性的类。每次你停留在这里,价格都会上涨50美元!
class Monopoly(object):
def __init__(self):
self.boardwalk_price = 500
@property
def boardwalk(self):
# In reality, this might represent a database call or time
# intensive task like calling a third-party API.
self.boardwalk_price += 50
return self.boardwalk_price
现在运行它
>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
600
现在让我们将boardwalk属性转换为cached_property。
from cached_property import cached_property
class Monopoly(object):
def __init__(self):
self.boardwalk_price = 500
@cached_property
def boardwalk(self):
# Again, this is a silly example. Don't worry about it, this is
# just an example for clarity.
self.boardwalk_price += 50
return self.boardwalk_price
现在当我们运行它时,价格保持在550美元。
>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
为什么monopoly.boardwalk的值没有改变?因为它是一个缓存属性!
清除缓存
缓存的函数结果可能会被外部力量失效。让我们演示如何强制缓存失效
>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
>>> # invalidate the cache
>>> del monopoly.__dict__['boardwalk']
>>> # request the boardwalk property again
>>> monopoly.boardwalk
600
>>> monopoly.boardwalk
600
与线程协同工作
如果很多人同时想留在Boardwalk,怎么办?这意味着使用线程,不幸的是,这会与标准cached_property产生问题。在这种情况下,切换到使用threaded_cached_property
from cached_property import threaded_cached_property
class Monopoly(object):
def __init__(self):
self.boardwalk_price = 500
@threaded_cached_property
def boardwalk(self):
"""threaded_cached_property is really nice for when no one waits
for other people to finish their turn and rudely start rolling
dice and moving their pieces."""
sleep(1)
self.boardwalk_price += 50
return self.boardwalk_price
现在使用它
>>> from threading import Thread
>>> from monopoly import Monopoly
>>> monopoly = Monopoly()
>>> threads = []
>>> for x in range(10):
>>> thread = Thread(target=lambda: monopoly.boardwalk)
>>> thread.start()
>>> threads.append(thread)
>>> for thread in threads:
>>> thread.join()
>>> self.assertEqual(m.boardwalk, 550)
与async/await协同工作(Python 3.5+)
缓存的属性可以是异步的,在这种情况下,您必须像往常一样使用await来获取值。由于缓存,值只计算一次然后缓存
from cached_property import cached_property
class Monopoly(object):
def __init__(self):
self.boardwalk_price = 500
@cached_property
async def boardwalk(self):
self.boardwalk_price += 50
return self.boardwalk_price
现在使用它
>>> async def print_boardwalk():
... monopoly = Monopoly()
... print(await monopoly.boardwalk)
... print(await monopoly.boardwalk)
... print(await monopoly.boardwalk)
>>> import asyncio
>>> asyncio.get_event_loop().run_until_complete(print_boardwalk())
550
550
550
请注意,这也与线程不兼容,大多数asyncio对象都不是线程安全的。如果您在每个线程中运行单独的事件循环,缓存的版本很可能会有错误的事件循环。总结一下,要么使用协作式多任务处理(事件循环),要么使用线程,但不要同时使用两者。
缓存超时
有时您希望物品的价格在一段时间后重置。使用cached_property和threaded_cached_property的ttl版本。
import random
from cached_property import cached_property_with_ttl
class Monopoly(object):
@cached_property_with_ttl(ttl=5) # cache invalidates after 5 seconds
def dice(self):
# I dare the reader to implement a game using this method of 'rolling dice'.
return random.randint(2,12)
现在使用它
>>> monopoly = Monopoly()
>>> monopoly.dice
10
>>> monopoly.dice
10
>>> from time import sleep
>>> sleep(6) # Sleeps long enough to expire the cache
>>> monopoly.dice
3
>>> monopoly.dice
3
注意: ttl工具并不能可靠地清除缓存。这就是为什么它们被拆分成了单独的工具。请参阅https://github.com/pydanny/cached-property/issues/16。
致谢
@pydanny,他为原始的cached-property实现做出了贡献。
Pip、Django、Werkzueg、Bottle、Pyramid和Zope,因为它们有自己的实现。这个包最初使用了一个与Bottle版本相匹配的实现。
Reinout Van Rees,他向我指出了cached_property装饰器。
@audreyr``_创建了``cookiecutter``_,这使得推出这个包只需@pydanny 15分钟。
@tinche指出线程问题并提供了解决方案。
@bcho提供了过期时间功能
历史记录
1.6.4 (2020-03-06)
修复一些遗留的Python 2支持代码(#25)
1.6.3 (2019-09-07)
解决cached_property文档字符串不显示的问题(#171)。
1.6.2 (2019-07-22)
修复元数据,以保留原始作者并添加@althonos作为维护者
1.6.1 (2019-07-22)
修复了setup.cfg中存在的不必要依赖项
1.6.0 (2019-07-22)
修复了类层次结构,cached_property现在继承自property
支持槽类,并停止使用对象__dict__
使用functools.update_wrapper改进函数包装
实现自Python 3.6以来可用的__set_name__魔法方法
1.5.1 (2018-08-05)
添加了对Python 3.7的正式支持
移除了对Python 3.3的正式支持
1.4.3 (2018-06-14)
从较旧版本的Python中捕获asyncio导入的SyntaxError,感谢@asottile
1.4.2 (2018-04-08)
真正修复了测试,感谢@pydanny
1.4.1 (2018-04-08)
将conftest.py添加到清单中,以便测试可以在tarball之外正常工作,感谢@dotlambda
确保新的asyncio测试不会破坏Debian上Python 2.7的构建,感谢@pydanny
通过black进行代码格式化,感谢@pydanny和@ambv
1.4.0 (2018-02-25)
添加了asyncio支持,感谢@vbraun
移除了Python 2.6支持,其生命周期的结束是5年前,感谢@pydanny
1.3.1 (2017-09-21)
验证Python 3.6
1.3.0 (2015-11-24)
从HISTORY.rst中删除了一些非ASCII字符,感谢@AdamWill
添加了对Python 3.5的官方支持,感谢@pydanny和@audreyr
移除了示例中放置不明确的锁,感谢@ionelmc
修正了无效化缓存文档,感谢 @proofit404
更新到最新的 Travis-CI 环境,感谢 @audreyr
1.2.0 (2015-04-28)
整体代码和测试重构,感谢 @gsakkis
允许使用 del 语句通过 ttl 重置缓存属性,而不是 del obj._cache[attr],感谢 @gsakkis。
在 PyPy 中发现了一个 bug,https://bitbucket.org/pypy/pypy/issue/2033/attributeerror-object-attribute-is-read,感谢 @gsakkis
修复了 threaded_cached_property_with_ttl 以使其真正线程安全,感谢 @gsakkis
1.1.0 (2015-04-04)
回归:由于缓存并非总是清除,我们已将过期时间特性分离到其自己的特定工具集中,感谢 @pydanny
修复了 README 中的错别字,感谢 @zoidbergwill
1.0.0 (2015-02-13)
将定时过期功能添加到 cached_property 装饰器中。
向下不兼容:将 del monopoly.boardwalk 更改为 del monopoly['boardwalk'] 以支持新的 TTL 功能。
0.1.5 (2014-05-20)
添加了新 threaded_cached_property 装饰器以支持多线程。
文档化了缓存无效化
更新了致谢
获取了 bottle 实现的来源
0.1.4 (2014-05-17)
修复了 py_modules 参数的问题。
0.1.3 (2014-05-17)
从 setup.py 中移除了包的导入。
0.1.2 (2014-05-17)
文档修复。不为此打开 RTFD 实例,因为使用它非常简单。
0.1.1 (2014-05-17)
setup.py 修复。哎呀!
0.1.0 (2014-05-17)
PyPI 上的第一个版本。