首页 > 解决方案 > Python冻结数据类,允许通过方法更改属性

问题描述

假设我有一个数据类:

@dataclass(frozen=True)
class Foo:
    id: str
    name: str

我希望这是不可变的(因此是frozen=True),这样就foo.id = bar失败foo.name = baz了。但是,我希望能够剥离 id,如下所示:

foo = Foo(id=10, name="spam")

foo.strip_id()
foo
-> Foo(id=None, name="spam")

我已经尝试了一些事情,覆盖setattr,但没有任何效果。有一个优雅的解决方案吗?(我知道我可以编写一个方法来返回一个相同的新冻结实例,除了该 id 已被剥离,但这似乎有点 hacky,它需要我这样做foo = foo.strip_id(),因为foo.strip_id()实际上不会改变foo

编辑:

尽管一些评论者似乎不同意,但我认为“完全可变,随心所欲”和“不可变,除非以这种特殊的、严格控制的方式”之间存在合理的区别

标签: pythonpython-dataclasses

解决方案


好吧,你可以通过直接修改__dict__实例的成员使用object.__setattr__(...)1修改属性来做到这一点,但为什么呢?专门要求不可变然后使其可变是......优柔寡断。但如果你必须:

from dataclasses import dataclass

@dataclass(frozen=True)
class Foo:
    id: str
    name: str
    def strip_id(self):
        object.__setattr__(self, 'id', None)

foo=Foo(10, 'bar')

>>> foo
Foo(id=10, name='bar')
>>> foo.strip_id()
>>> foo
Foo(id=None, name='bar')

这样做的任何方式都可能看起来很老套……因为它需要做与设计根本相反的事情。

如果您将此用作向其他程序员发出他们不应修改值的信号,则通常在 Python 中完成的方式是在变量名称前加上一个下划线。如果您想这样做,同时使值可访问,Python 有一个名为 的内置模块property,其中(来自文档)“典型用途是定义托管属性”:

from dataclasses import dataclass

@dataclass
class Foo:
    _name: str
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self, value):
        self._name = value
    @name.deleter
    def name(self):
        self._name = None

然后你可以像这样使用它:

>>> f=Foo()
>>> f.name = "bar"
>>> f.name
'bar'
>>> f._name
'bar'
>>> del f.name
>>> f.name
>>> f._name

_name装饰方法隐藏了behind的实际值,name以控制用户如何与该值交互。您可以使用它在存储或返回数据之前将转换规则或验证检查应用于数据。

这与 using 完成的事情并不完全一样@dataclass(frozen=True),如果您尝试将其声明为冻结,您将收到错误消息。将冻结的数据类与属性装饰器混合起来并不简单,我还没有看到一个简洁直观的令人满意的解决方案。@Arne 发布了这个答案,我在 GitHub 上找到了这个帖子,但是这两种方法都不是很鼓舞人心;如果我在必须维护的代码中遇到这样的事情,我不会很高兴(但我感到困惑,并且可能会很恼火)。


1:根据@Arne 的回答进行了修改,他观察到不能保证内部使用字典作为数据容器。


推荐阅读