跳转到主要内容

方法和属性缓存装饰器

项目描述

zope.cachedescriptors

Latest release Supported Python versions https://github.com/zopefoundation/zope.cachedescriptors/actions/workflows/tests.yml/badge.svg Documentation Status https://coveralls.io/repos/github/zopefoundation/zope.cachedescriptors/badge.svg?branch=master

缓存描述符缓存其输出。它们考虑了它们依赖的实例属性,因此当实例属性更改时,描述符将更改它们返回的值。

缓存描述符将数据缓存在_v_属性中,因此它们也用于管理持久对象的易变属性的计算。

持久描述符

  • property

    一个简单的计算属性。

    src/zope/cachedescriptors/property.rst.

  • method

    幂等方法。返回值基于方法参数以及方法定义中指定的任何实例属性。

    src/zope/cachedescriptors/method.rst.

缓存属性

缓存属性是计算属性,它们缓存其计算值。它们考虑了它们依赖的实例属性,因此当实例属性更改时,属性将更改它们返回的值。

CachedProperty

缓存属性将数据缓存在_v_属性中,因此它们也用于管理持久对象的易变属性的计算。以下是一个例子

>>> from zope.cachedescriptors import property
>>> import math
>>> class Point:
...
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
...     @property.CachedProperty('x', 'y')
...     def radius(self):
...         print('computing radius')
...         return math.sqrt(self.x**2 + self.y**2)
>>> point = Point(1.0, 2.0)

如果第一次请求半径

>>> '%.2f' % point.radius
computing radius
'2.24'

我们看到半径函数被调用,但如果再次请求它

>>> '%.2f' % point.radius
'2.24'

函数不会被调用。如果我们更改半径依赖的一个属性,它将被重新计算

>>> point.x = 2.0
>>> '%.2f' % point.radius
computing radius
'2.83'

但更改其他属性不会引起重新计算

>>> point.q = 1
>>> '%.2f' % point.radius
'2.83'

注意我们没有添加任何非易失性属性

>>> names = [name for name in point.__dict__ if not name.startswith('_v_')]
>>> names.sort()
>>> names
['q', 'x', 'y']

为了向后兼容,同样的内容也可以使用装饰器语法来交替编写

>>> class Point:
...
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
...     def radius(self):
...         print('computing radius')
...         return math.sqrt(self.x**2 + self.y**2)
...     radius = property.CachedProperty(radius, 'x', 'y')
>>> point = Point(1.0, 2.0)

如果第一次请求半径

>>> '%.2f' % point.radius
computing radius
'2.24'

我们看到半径函数被调用,但如果再次请求它

>>> '%.2f' % point.radius
'2.24'

函数不会被调用。如果我们更改半径依赖的一个属性,它将被重新计算

>>> point.x = 2.0
>>> '%.2f' % point.radius
computing radius
'2.83'

如果通过类访问属性,则保留文档和__name__。这允许Sphinx提取文档。

>>> class Point:
...
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
...     @property.CachedProperty('x', 'y')
...     def radius(self):
...         '''The length of the line between self.x and self.y'''
...         print('computing radius')
...         return math.sqrt(self.x**2 + self.y**2)
>>> print(Point.radius.__doc__)
The length of the line between self.x and self.y
>>> print(Point.radius.__name__)
radius

可以指定一个没有依赖关系的CachedProperty。为了向后兼容,这可以以几种不同的方式编写

>>> class Point:
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
...     @property.CachedProperty
...     def no_deps_no_parens(self):
...         print("No deps, no parens")
...         return 1
...
...     @property.CachedProperty()
...     def no_deps(self):
...         print("No deps")
...         return 2
...
...     def no_deps_old_style(self):
...         print("No deps, old style")
...         return 3
...     no_deps_old_style = property.CachedProperty(no_deps_old_style)


>>> point = Point(1.0, 2.0)
>>> point.no_deps_no_parens
No deps, no parens
1
>>> point.no_deps_no_parens
1
>>> point.no_deps
No deps
2
>>> point.no_deps
2
>>> point.no_deps_old_style
No deps, old style
3
>>> point.no_deps_old_style
3

延迟计算的属性

property模块提供了另一个支持略有不同缓存模型的描述符:延迟属性。像缓存的属性一样,它们在第一次使用时进行计算。然而,它们不存储在易失性属性中,也不会在其他属性更改时自动更新。此外,它们使用它们的属性名存储数据,从而覆盖了自己。这为计算后的属性提供了更快的属性访问。让我们看看使用延迟属性的先前列表

>>> class Point:
...
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
...     @property.Lazy
...     def radius(self):
...         print('computing radius')
...         return math.sqrt(self.x**2 + self.y**2)
>>> point = Point(1.0, 2.0)

如果第一次请求半径

>>> '%.2f' % point.radius
computing radius
'2.24'

我们看到半径函数被调用,但如果再次请求它

>>> '%.2f' % point.radius
'2.24'

函数不会被调用。如果我们更改半径依赖的属性之一,它仍然不会被调用

>>> point.x = 2.0
>>> '%.2f' % point.radius
'2.24'

如果我们想重新计算半径,我们必须手动删除它

>>> del point.radius
>>> point.x = 2.0
>>> '%.2f' % point.radius
computing radius
'2.83'

注意半径存储在实例字典中

>>> '%.2f' % point.__dict__['radius']
'2.83'

延迟属性需要知道属性名。它通常从传递给函数的名称推断属性名。如果我们想使用不同的名称,我们需要传递它

>>> def d(point):
...     print('computing diameter')
...     return 2*point.radius
>>> Point.diameter = property.Lazy(d, 'diameter')
>>> '%.2f' % point.diameter
computing diameter
'5.66'

如果通过类访问属性,则保留文档和__name__。这允许Sphinx提取文档。

>>> class Point:
...
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
...     @property.Lazy
...     def radius(self):
...         '''The length of the line between self.x and self.y'''
...         print('computing radius')
...         return math.sqrt(self.x**2 + self.y**2)
>>> print(Point.radius.__doc__)
The length of the line between self.x and self.y
>>> print(Point.radius.__name__)
radius

通过实例访问属性时,属性的文档将与返回值相同

>>> p = Point(1.0, 2.0)
>>> p.radius.__doc__ == float.__doc__
computing radius
True

这与标准Python property装饰器具有相同的行为。

readproperty

readproperty类似于延迟计算的属性,除了属性不是由属性设置的

>>> class Point:
...
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
...     @property.readproperty
...     def radius(self):
...         print('computing radius')
...         return math.sqrt(self.x**2 + self.y**2)
>>> point = Point(1.0, 2.0)
>>> '%.2f' % point.radius
computing radius
'2.24'
>>> '%.2f' % point.radius
computing radius
'2.24'

但是,你可以通过设置值来替换属性。这是与内置的property的主要区别

>>> point.radius = 5
>>> point.radius
5

如果通过类访问属性,则保留文档和__name__。这允许Sphinx提取文档。

>>> class Point:
...
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
...     @property.readproperty
...     def radius(self):
...         '''The length of the line between self.x and self.y'''
...         print('computing radius')
...         return math.sqrt(self.x**2 + self.y**2)
>>> print(Point.radius.__doc__)
The length of the line between self.x and self.y
>>> print(Point.radius.__name__)
radius

cachedIn

cachedIn属性允许指定存储计算值的属性

>>> class Point:
...
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
...     @property.cachedIn('_radius_attribute')
...     def radius(self):
...         print('computing radius')
...         return math.sqrt(self.x**2 + self.y**2)
>>> point = Point(1.0, 2.0)
>>> '%.2f' % point.radius
computing radius
'2.24'
>>> '%.2f' % point.radius
'2.24'

半径缓存在给定名称的属性中,在本例中是_radius_attribute

>>> '%.2f' % point._radius_attribute
'2.24'

当删除属性时,半径将重新计算一次。这允许无效化

>>> del point._radius_attribute
>>> '%.2f' % point.radius
computing radius
'2.24'
>>> '%.2f' % point.radius
'2.24'

如果通过类访问属性,则保留文档。这允许Sphinx提取文档。

>>> class Point:
...
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
...     @property.cachedIn('_radius_attribute')
...     def radius(self):
...         '''The length of the line between self.x and self.y'''
...         print('computing radius')
...         return math.sqrt(self.x**2 + self.y**2)
>>> print(Point.radius.__doc__)
The length of the line between self.x and self.y

方法缓存

cachedIn

cachedIn属性允许指定存储计算值的属性

>>> import math
>>> from zope.cachedescriptors import method
>>> class Point(object):
...
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
...     @method.cachedIn('_cache')
...     def distance(self, x, y):
...         """Compute the distance"""
...         print('computing distance')
...         return math.hypot(self.x - x, self.y - y)
...
>>> point = Point(1.0, 2.0)

值只计算一次

>>> point.distance(2, 2)
computing distance
1.0
>>> point.distance(2, 2)
1.0

使用不同的参数计算新的距离

>>> point.distance(5, 2)
computing distance
4.0
>>> point.distance(5, 2)
4.0

数据存储在给定的_cache属性中

>>> isinstance(point._cache, dict)
True
>>> sorted(point._cache.items())
[(((2, 2), ()), 1.0), (((5, 2), ()), 4.0)]

可以显式使数据无效

>>> point.distance.invalidate(point, 5, 2)
>>> point.distance(5, 2)
computing distance
4.0

使不在缓存中的键无效不会导致错误

>>> point.distance.invalidate(point, 47, 11)

函数的文档被保留(无论是通过实例还是类),允许Sphinx提取它

>>> print(point.distance.__doc__)
Compute the distance
>>> print(point.distance.__name__)
distance

>>> print(Point.distance.__doc__)
Compute the distance
>>> print(Point.distance.__name__)
distance

可以传递一个用于缓存属性的工厂。创建另一个Point类

>>> class MyDict(dict):
...     pass
>>> class Point(object):
...
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...
...     @method.cachedIn('_cache', MyDict)
...     def distance(self, x, y):
...         print('computing distance')
...         return math.sqrt((self.x - x)**2 + (self.y - y)**2)
...
>>> point = Point(1.0, 2.0)
>>> point.distance(2, 2)
computing distance
1.0

现在缓存是MyDict实例

>>> isinstance(point._cache, MyDict)
True

更改

5.0 (2023-03-27)

  • 添加对Python 3.11的支持。

  • 停止支持Python 2.7、3.5、3.6。

4.4 (2022-09-07)

  • 停止支持Python 3.4。

  • 添加对Python 3.7、3.8、3.9、3.10的支持。

4.3.1 (2017-12-09)

  • 修复将在即将发布的Python 3.7版本中中断的测试。

4.3.0 (2017-07-27)

  • 添加对Python 3.6的支持。

  • 停止支持Python 3.3。

4.2.0 (2016-09-05)

  • 添加对Python 3.5的支持。

  • 停止支持Python 2.6和3.2。

  • property 模块中获取的所有属性都保留了底层函数的文档字符串,除了 cachedIn,其余都保留了 functools.update_wrapper 保留的所有内容。

  • property.CachedProperty 可用作装饰器,带或不带依赖属性名称。

  • method.cachedIn 保留了底层函数的文档字符串,以及 functools.wraps 保留的所有内容。

4.1.0 (2014-12-26)

  • 添加对 PyPy 和 PyPy3 的支持。

  • 添加对 Python 3.4 的支持。

  • 添加对 Travis 上的测试支持。

4.0.0 (2013-02-13)

  • 放弃对 Python 2.4 和 2.5 的支持。

  • 添加对 Python 3.2 和 3.3 的支持。

3.5.1 (2010-04-30)

  • 删除对 zope.testing 的未声明测试依赖。

3.5.0 (2009-02-10)

  • 通过允许指定 zope.cachedescriptors.method.cachedIn 的存储工厂(默认为 dict)来删除对 ZODB 的依赖。如果您需要使用 BTree,必须将其作为 factory 参数传递给 zope.cachedescriptors.method.cachedIn 装饰器。

  • 删除与 zpkg 相关的文件。

  • 对包描述和文档进行一些清理。

  • 将包邮件列表地址更改为 zope-dev@zope.org,因为 zope3-dev@zope.org 现已停用。

3.4.0 (2007-08-30)

作为独立包的首次发布。

下载文件

下载适合您平台的应用程序文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。

源分布

zope.cachedescriptors-5.0.tar.gz (13.2 kB 查看哈希值)

上传时间

构建分布

zope.cachedescriptors-5.0-py3-none-any.whl (13.1 kB 查看哈希值)

上传时间 Python 3

由以下支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面