Python 冻结数据类,允许通过方法更改属性

ale*_*ord 8 python python-dataclasses

假设我有一个数据类:

@dataclass(frozen=True)
class Foo:
    id: str
    name: str
Run Code Online (Sandbox Code Playgroud)

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

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

foo.strip_id()
foo
-> Foo(id=None, name="spam")
Run Code Online (Sandbox Code Playgroud)

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

编辑:

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

Z4-*_*ier 8

好吧,你可以通过直接修改__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')
Run Code Online (Sandbox Code Playgroud)

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

如果您将此作为向其他程序员发出的信号,表明他们不应修改这些值,则 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
Run Code Online (Sandbox Code Playgroud)

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

>>> f=Foo()
>>> f.name = "bar"
>>> f.name
'bar'
>>> f._name
'bar'
>>> del f.name
>>> f.name
>>> f._name
Run Code Online (Sandbox Code Playgroud)

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

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


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

  • @Z4-tier 不,不要在Python中使用getter。 (4认同)
  • 并不真地。我的意思是,你想保护谁免受伤害?您是否有 Java 背景,正在寻找更像类私有成员的东西? (3认同)

Arn*_*rne 5

作为对 Z4-tier 解决方案的轻微改进,请使用object.__setattr__而不是self.__dict__操作冻结数据类的属性。事实上,类使用字典来存储其属性只是默认行为,尤其是数据类将经常使用字典,__slots__因为它减少了内存占用。

from dataclasses import dataclass

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

    def strip_id(self):
        object.__setattr__(self, 'a', None)   
Run Code Online (Sandbox Code Playgroud)