用于在类中缓存属性的装饰器。
项目描述
用于在类中缓存属性的装饰器。
为什么?
使得缓存耗时或计算量大的属性变得快捷且简单。
因为我厌倦了将这段代码从非Web项目复制粘贴到非Web项目中。
我需要一些真正简单且在Python 2和3中都有效的东西。
如何使用它
让我们定义一个具有昂贵属性的类。每次你待在那里,价格就会上涨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。
致谢
Pip, Django, Werkzueg, Bottle, Pyramid 和 Zope 都有自己的实现。这个包最初使用了一个与 Bottle 版本匹配的实现。
Reinout Van Rees 指出了 cached_property 装饰器。
我的妻子 @audreyr 创建了 cookiecutter,这意味着推出这个项目只花了我 15 分钟。
@tinche 指出了线程问题,并提供了解决方案。
@bcho 提供了过期功能
支持此项目
此项目由志愿者维护。通过宣传以下内容来支持他们的努力:
Django 快速入门教程
Django 快速入门教程是宇宙中最好的以奶酪为主题的 Django 参考书!
历史
1.5.2 (2020-09-21)
为 Python 3.8 添加正式支持
移除对 Python 3.4 的正式支持
从 Travis 切换到 GitHub actions
使测试通过 flake8 Python 2.7
1.5.1 (2018-08-05)
添加了对 Python 3.7 的正式支持
移除了对 Python 3.3 的正式支持
1.4.3 (2018-06-14)
由于 @asottile 的帮助,从较旧版本的 Python 中捕获 asyncio import 的 SyntaxError
1.4.2 (2018-04-08)
由于 @pydanny 的帮助,真正修复了测试
1.4.1 (2018-04-08)
由于 @dotlambda 的帮助,向清单中添加了 conftest.py,以便测试在 tarball 上正确运行
由于 @pydanny 的帮助,确保新的 asyncio 测试不会破坏 Debian 上的 Python 2.7 构建
通过 black 进行代码格式化,感谢 @pydanny 和 @ambv
1.4.0 (2018-02-25)
由于 @vbraun 的帮助,添加了 asyncio 支持
移除对 Python 2.6 的支持,其生命终结已经过去 5 年,感谢 @pydanny
1.3.1 (2017-09-21)
验证 Python 3.6
1.3.0 (2015-11-24)
从 HISTORY.rst 中删除一些非 ASCII 字符,感谢 @AdamWill
由于 @pydanny 和 @audreyr 的帮助,添加了对 Python 3.5 的官方支持
由于 @ionelmc 的帮助,移除了示例中放置不合适的锁
由于 @proofit404 的帮助,修正了缓存无效化的文档
由于 @audreyr 的帮助,更新到最新的 Travis-CI 环境
1.2.0 (2015-04-28)
整体代码和测试重构,感谢 @gsakkis
允许使用 del 语句通过 ttl 重置缓存的属性,而不是 del obj._cache[attr],感谢 @gsakkis。
由于 @gsakkis 的帮助,发现了 PyPy 的一个错误,https://bitbucket.org/pypy/pypy/issue/2033/attributeerror-object-attribute-is-read
由于 @gsakkis 的帮助,修复了 threaded_cached_property_with_ttl 以使其真正线程安全
1.1.0 (2015-04-04)
回归:由于缓存并非总是清除,我们已将过期功能分离到其自己的特定工具集中,感谢 @pydanny
由于 @zoidbergwill 的帮助,修复了 README 中的错误
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。
项目详情
cached-property-1.5.2.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130 |
|
MD5 | 3451c63f8733ea0756ca1dd2b0c04bb8 |
|
BLAKE2b-256 | 612cd21c1c23c2895c091fa7a91a54b6872098fea913526932d21902088a7c41 |
cached-property-1.5.2-py2.py3-none-any.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0 |
|
MD5 | 8f5dbd53d7e6cf98818454c6be4e76fa |
|
BLAKE2b-256 | 4819f2090f7dad41e225c7f2326e4cfe6fff49e57dedb5b53636c9551f86b069 |