跳转到主要内容

用于在类中缓存属性的装饰器。

项目描述

https://img.shields.io/pypi/v/cached-property.svg https://github.com/pydanny/cached-property/workflows/Python%20package/badge.svg Code style: black

用于在类中缓存属性的装饰器。

为什么?

  • 使得缓存耗时或计算量大的属性变得快捷且简单。

  • 因为我厌倦了将这段代码从非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_propertythreaded_cached_propertyttl 版本。

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 Crash Course

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)

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 (12.2 kB 查看哈希值)

上传时间

构建分布

cached_property-1.5.2-py2.py3-none-any.whl (7.6 kB 查看哈希值)

上传时间 Python 2 Python 3

由以下支持