跳转到主要内容

Python编程中有用的属性变体(所需属性、可写属性、缓存属性等)

项目描述

https://travis-ci.org/xolox/python-property-manager.svg?branch=master https://coveralls.io/repos/github/xolox/python-property-manager/badge.svg?branch=master

属性管理器 包定义了多个自定义的Python编程属性变体,包括所需属性、可写属性、缓存属性等。目前已在Python 2.7、3.5、3.6、3.7、3.8和PyPy上进行测试。有关使用说明,请参阅文档

状态

属性管理器 包最初是我的executor 包的一个子模块,在那里我想要定义具有许多属性(具有在需要时计算出的默认值)的类,同时也需要支持赋值以轻松覆盖默认值。

自从我创建了那个模块以来,我一直想在其他几个我正在工作的项目中重用它,但只为 property_manager 子模块添加 executor 依赖感觉有点难看。

这是决定创建 property-manager 包的时候。当我从 executor 中提取子模块时,对其实现进行了重大修改(使代码更加健壮和灵活),并在过程中改进了测试、文档和覆盖率。

安装

property-manager 包可在 PyPI 上找到,这意味着安装应该非常简单

$ pip install property-manager

实际上,有无数种安装 Python 包的方法(例如,例如,按用户站点包目录虚拟环境,或仅安装系统级)我无意在这里展开讨论,所以如果你感到害怕,请在返回这些说明之前了解你的选项 ;-)。

使用

本节展示了如何使用最有用的属性子类。请参阅文档获取更详细的信息。

可写属性

使用 writable_property 装饰器创建具有计算默认值的可写属性非常容易

from random import random
from property_manager import writable_property

class WritablePropertyDemo(object):

    @writable_property
    def change_me(self):
        return random()

首先,让我们看看计算默认值是如何表现的

>>> instance = WritablePropertyDemo()
>>> print(instance.change_me)
0.13692489329941815
>>> print(instance.change_me)
0.8664002331885933

如你所见,每次都会重新计算值。现在我们将给它赋一个值

>>> instance.change_me = 42
>>> print(instance.change_me)
42

从现在开始,change_me 将是数字 42,并且无法恢复到计算值

>>> delattr(instance, 'change_me')
Traceback (most recent call last):
  File "property_manager/__init__.py", line 584, in __delete__
    raise AttributeError(msg % (obj.__class__.__name__, self.__name__))
AttributeError: 'WritablePropertyDemo' object attribute 'change_me' is read-only

如果你需要一个同时支持赋值和删除(清除分配的值)的属性,你可以使用 mutable_property

所需属性

可以使用 required_property 装饰器创建必需属性

from property_manager import PropertyManager, required_property

class RequiredPropertyDemo(PropertyManager):

    @required_property
    def important(self):
        """A very important attribute."""

属性必需意味着什么?让我们创建一个类的实例并找出答案

>>> instance = RequiredPropertyDemo()
Traceback (most recent call last):
  File "property_manager/__init__.py", line 131, in __init__
    raise TypeError("%s (%s)" % (msg, concatenate(missing_properties)))
TypeError: missing 1 required argument (important)

因此,当属性未给出值时,类的构造函数会引发异常。我们可以通过向构造函数提供关键字参数来给属性赋值

>>> instance = RequiredPropertyDemo(important=42)
>>> print(instance)
RequiredPropertyDemo(important=42)

我们也可以给属性赋新值

>>> instance.important = 13
>>> print(instance)
RequiredPropertyDemo(important=13)

缓存属性

支持两种类型的缓存属性,我们在这里展示两种

from random import random
from property_manager import cached_property, lazy_property

class CachedPropertyDemo(object):

    @cached_property
    def expensive(self):
        print("Calculating expensive property ..")
        return random()

    @lazy_property
    def non_idempotent(self):
        print("Calculating non-idempotent property ..")
        return random()

cached_property 装饰器创建的属性在需要时计算属性值并将结果缓存起来

>>> instance = CachedPropertyDemo()
>>> print(instance.expensive)
Calculating expensive property ..
0.763863180683
>>> print(instance.expensive)
0.763863180683

可以通过使缓存值无效来使属性重新计算其值

>>> del instance.expensive
>>> print(instance.expensive)
Calculating expensive property ..
0.396322737214
>>> print(instance.expensive)
0.396322737214

现在你了解了 cached_property,解释 lazy_property 非常简单:它根本不支持缓存值的无效化!下面是如何在实际中工作的

>>> instance.non_idempotent
Calculating non-idempotent property ..
0.27632566561900895
>>> instance.non_idempotent
0.27632566561900895
>>> del instance.non_idempotent
Traceback (most recent call last):
  File "property_manager/__init__.py", line 499, in __delete__
    raise AttributeError(msg % (obj.__class__.__name__, self.__name__))
AttributeError: 'CachedPropertyDemo' object attribute 'non_idempotent' is read-only
>>> instance.non_idempotent
0.27632566561900895

基于环境变量的属性

custom_property 类的构造函数(及其子类)接受关键字参数 环境变量,可以从环境中获取属性的值

from random import random
from property_manager import mutable_property

class EnvironmentPropertyDemo(object):

    @mutable_property(environment_variable='WHATEVER_YOU_WANT')
    def environment_based(self):
        return 'some-default-value'

默认情况下,属性的值按预期计算

>>> instance = EnvironmentPropertyDemo()
>>> print(instance.environment_based)
some-default-value

当设置了环境变量时,它将覆盖计算值

>>> os.environ['WHATEVER_YOU_WANT'] = '42'
>>> print(instance.environment_based)
42

给属性赋值将覆盖来自环境变量的值

>>> instance.environment_based = '13'
>>> print(instance.environment_based)
13

删除属性清除分配的值,以便属性回退到环境

>>> delattr(instance, 'environment_based')
>>> print(instance.environment_based)
42

如果我们现在也清除环境变量,则属性回退到计算值

>>> os.environ.pop('WHATEVER_YOU_WANT')
'42'
>>> print(instance.environment_based)
some-default-value

支持设置器和删除器

所有自定义属性类都支持设置器和删除器,就像 Python 的 property 装饰器一样。

PropertyManager

当你定义一个继承自 PropertyManager 类的类时,以下行为将可用于你的类

  • 必需属性在没有设置时引发异常。

  • 可以通过向你的类的构造函数传递关键字参数来设置可写属性的值。

  • 您对象的 repr() 将渲染类的名称以及所有属性的名称和值。可以轻松排除单个属性从 repr() 输出中。

  • 可以使用 clear_cached_properties() 方法一次性使所有缓存的属性值无效。

此外,您可以将 property_manager.sphinx 模块作为 Sphinx 扩展使用,以自动生成提供基类、属性、公共方法和特殊方法概述的模板文档。

类似项目

Python 包索引包含相当多的包,它们提供具有类似语义的自定义属性。

cached-property

在我的个人最爱中,直到我编写了自己的版本。此包提供几种缓存的属性变体。它支持多线程和时间基础缓存失效,这是 property-manager 不支持的。

lazy-property

此包提供两种缓存的属性变体:只读属性和可写属性。两种变体都会无限期地缓存计算值。

memoized-property

此包提供一种属性变体,它简单地无限期地缓存计算值。

property-caching

此包提供几种缓存的属性变体,支持类属性、对象属性和缓存失效。

propertylib

此包使用元类来实现定义计算属性的替代语法。它定义了几个与 property-manager 包中定义的语义相似的属性变体。

rwproperty

此包使用替代语法实现计算的可写属性。

独特特性

尽管存在上述所有现有的 Python 包,我还是决定创建并发布 property-manager 包,因为了解 Python 的 描述符协议 很有趣,而且我脑海中想到了一些在其他地方找不到的功能。

  • 一个根据构造函数参数设置可写属性的超类。

  • 一个理解必需属性并会在必需属性未正确初始化时引发明确异常的超类。

  • 清晰地区分惰性属性(其计算值被缓存但无法失效,因为这会损害内部状态)和缓存属性(其计算值被缓存但可以失效以计算新值)。

  • 一种快速使对象的所有缓存的属性失效的方法。

  • 一种轻松更改自定义属性语义的方法,例如,如果用户想要可写的缓存属性怎么办?使用 property-manager,通过组合现有语义轻松定义新的属性变体。

    from property_manager import cached_property
    
    class WritableCachedPropertyDemo(object):
    
        @cached_property(writable=True)
        def expensive_overridable_attribute(self):
            """Expensive calculations go here."""

    上面的例子创建了一个新的匿名类,然后立即使用该类来装饰方法。我们也可以给这个类起一个名字。

    from property_manager import cached_property
    
    writable_cached_property = cached_property(writable=True)
    
    class WritableCachedPropertyDemo(object):
    
        @writable_cached_property
        def expensive_overridable_attribute(self):
            """Expensive calculations go here."""

    通过给新的属性变体起一个名字,它可以被重复使用。我们可以更进一步,正确地记录新的属性变体。

    from property_manager import cached_property
    
    class writable_cached_property(cached_property):
    
        """A cached property that supports assignment."""
    
        writable = True
    
    class WritableCachedPropertyDemo(object):
    
        @writable_cached_property
        def expensive_overridable_attribute(self):
            """Expensive calculations go here."""

    多年来我一直使用 Python 中的计算属性,在这些年里,我了解到不同的 Python 项目对自定义属性变体有不同的要求。预先定义所有可能的排列是疯狂的行为,但我认为 property-manager 包提供的灵活性使适应变得很远。这是我最困扰所有其他实现属性变体的 Python 包的一件事:它们不易适应未预见的用例。

联系

最新版本的 property-manager 可在 PyPIGitHub 上获取。文档托管在 Read the Docs 上,包括一个 变更日志。如需提交错误报告,请在 GitHub 上创建问题。如果您有任何问题或建议,请随时通过电子邮件peter@peterodding.com联系我。

许可

此软件受 MIT 许可证 许可。

© 2020 Peter Odding。

项目详情


下载文件

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

源代码发行版

property-manager-3.0.tar.gz (33.7 kB 查看哈希值)

上传日期 源代码

构建发行版

property_manager-3.0-py2.py3-none-any.whl (23.9 kB 查看哈希值)

上传日期 Python 2 Python 3

支持者

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