【问题标题】:How to make attribute in dataclass read-only?如何使数据类中的属性只读?
【发布时间】:2020-07-28 22:02:56
【问题描述】:

假设我有这样的课程:

class C:

    def __init__(self, stuff: int):
        self._stuff = stuff

    @property
    def stuff(self) -> int:
        return self._stuff

那么stuff 是只读的:

c = C(stuff=10)
print(c.stuff)  # prints 10

c.stuff = 2

按预期失败

AttributeError: 无法设置属性

如何使用数据类获得相同的行为?如果我也想拥有setter,我可以这样做:

@dataclass
class DC:
    stuff: int
    _stuff: int = field(init=False, repr=False)

    @property
    def stuff(self) -> int:
        return self._stuff

    @stuff.setter
    def stuff(self, stuff: int):
        self._stuff = stuff

但是没有@stuff.setter 部分我怎么能做到呢?

【问题讨论】:

  • 省略二传手?为什么数据类部分在这里相关?
  • @jonrsharpe: 忽略setter doe snot help,然后我仍然可以设置stuff: dc = DC(stuff=10), dc.stuff = 2 将工作得“很好”;但它也应该失败。
  • 也许这对这个问题有帮助 - 你的意思是包含一个引发错误的 setter 实现吗?
  • @juanpa.arrivillaga:这似乎冻结了所有属性。如果我有例如stuffmore_stuff 但只想将 stuff 设置为只读,但 more_stuff 应该是可设置的,这似乎不起作用。
  • 是的,这不是一个直接的解决方案。似乎数据类在这里不太适合。也许我会玩field,如果我让它按照你想要的方式工作,我会在这里写一个答案。

标签: python python-3.x python-dataclasses


【解决方案1】:
from dataclasses import dataclass

@dataclass(frozen=True)
class YourClass:
    """class definition"""

https://docs.python.org/3/library/dataclasses.html#frozen-instances

在类实例化后,当尝试更改其任何属性时,将引发异常。

【讨论】:

  • 谢谢,但这会冻结所有属性,但我只想冻结某些属性,而不是全部...
【解决方案2】:

要获得dataclass 提供的样板减少,我发现唯一的方法是使用描述符。

In [236]: from dataclasses import dataclass, field
In [237]: class SetOnce:
     ...:     def __init__(self):
     ...:         self.block_set = False
     ...:     def __set_name__(self, owner, attr):
     ...:         self.owner = owner.__name__
     ...:         self.attr = attr
     ...:     def __get__(self, instance, owner):
     ...:         return getattr(instance, f"_{self.attr}")
     ...:     def __set__(self, instance, value):
     ...:         if not self.block_set:
     ...:             self.block_set = True
     ...:             setattr(instance, f"_{self.attr}", value)
     ...:         else:
     ...:             raise AttributeError(f"{self.owner}.{self.attr} cannot be set.")

In [239]: @dataclass
     ...: class Foo:
     ...:     bar:str = field(default=SetOnce())

In [240]: test = Foo("bar")

In [241]: test.bar
Out[241]: 'bar'

In [242]: test.bar = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-242-9cc7975cd08b> in <module>
----> 1 test.bar = 1

<ipython-input-237-bddce9441c9a> in __set__(self, instance, value)
     12             self.value = value
     13         else:
---> 14             raise AttributeError(f"{self.owner}.{self.attr} cannot be set.")
     15

AttributeError: Foo.bar cannot be set.

In [243]: test
Out[247]: Foo(bar='bar')

【讨论】:

  • 请看我的编辑,当我写这篇文章时,我还在学习描述符。好吧,我仍然是,但我意识到我需要将值存储在实例的私有属性上,而不是描述符实例(附加到类)上。
【解决方案3】:

因为在类定义中使用装饰器本质上会触发@dataclass 装饰器以将属性对象用作默认字段,所以效果不佳。您可以在外部设置属性,例如:

>>> from dataclasses import dataclass, field
>>> @dataclass
... class DC:
...     _stuff: int = field(repr=False)
...     stuff: int = field(init=False)
...
>>> DC.stuff = property(lambda self: self._stuff) # dataclass decorator cant see this
>>> dc = DC(42)
>>> dc
DC(stuff=42)
>>> dc.stuff
42
>>> dc.stuff = 99
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

【讨论】:

  • 嗯,这行得通,但我不能这样做dc = DC(stuff=10)。那我真的用的是普通班……
  • @Cleb 你可以实现一个带有一些标志的设置器,如果它被设置一次,然后不允许进一步设置,但是是的,它开始失去它的便利因素。
  • 使用这种方法会使repr 变得丑陋,正如@Cleb 指出的那样,init 也变得丑陋。真的很不幸,他们在dataclasses.field 中没有frozen,这真的没有意义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-07-24
  • 1970-01-01
  • 2011-03-20
  • 2011-11-08
  • 1970-01-01
  • 2010-12-14
  • 2016-11-26
相关资源
最近更新 更多