【问题标题】:Python frozen dataclass, allow changing of attribute via methodPython冻结数据类,允许通过方法更改属性
【发布时间】:2020-03-16 01:02:59
【问题描述】:

假设我有一个数据类:

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

我希望它是不可变的(因此是 frozen=True),这样 foo.id = barfoo.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)

编辑:

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

【问题讨论】:

  • 你希望它是不可变的,除了你希望它是可变的。您的目标存在根本矛盾。
  • foo = foo.strip_id() 是一件非常好的事情。例如,字符串方法就是这样工作的。
  • 你不能有可变的、不可变的对象。对不起。选一个。另外:“我知道我可以编写一个方法来返回一个相同的新冻结实例,除了该 id 已被剥离,但这似乎有点 hacky”根本不是 hacky。事实上,这通常被认为是最佳实践,有整个语言可以防止可变状态,这就是对象在这些语言中的工作方式。我会选择那条路线。

标签: python python-dataclasses


【解决方案1】:

嗯,你可以通过直接修改实例的__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 的实际值隐藏在name 后面,以控制用户如何与该值交互。您可以使用它在存储或返回数据之前对数据应用转换规则或验证检查。

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


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

【讨论】:

  • 这确实有效,谢谢。我已经编辑了我的问题以发表相同的评论,但“以非常具体、严格控制的方式可变”和“完全可变”之间肯定存在合理的区别吗?
  • 并非如此。我的意思是,你想保护谁?您是否偶然地来自 Java 背景,并且正在寻找更像类私有成员的东西?
  • 不,没有java背景。它用于公司内部库,并且该对象应该很少更改,并且只能以这种特定方式更改。我正在防止图书馆用户滥用数据类型,这会带来安全问题。
  • 如果只是为了防止其他程序员错误地使用这段数据,那么传统上在 Python 中通信的方式是在变量名前加上下划线(单下划线,我们不做邓斯在这里)。这通常被理解为“私有,不要修改”,它还会阻止它通过通配符导入。
  • @Z4-tier 不,不要在 Python 中使用 getter。
【解决方案2】:

作为对Z4层解决方案的轻微改进,请使用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)   

【讨论】:

  • 我将此添加到我的答案中,但由于这是一个很好的观察结果而赞成。
【解决方案3】:

除了这个object.__setattr__(self, 'id', None) hack-alike,在__init__ 方法中尤其丑陋,难道不能有一个可选的标记值(例如pytest 中的NOTSET),它可以被修改为字段类型的具体值,只有一次? 这将很容易实现,并允许灵活地设置不可变性。

【讨论】:

    猜你喜欢
    • 2020-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-12
    • 2015-06-01
    • 2018-09-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多