在不使用python数据类的setter的情况下拥有属性getter的pythonic方法是什么?

时间:2019-11-20 07:12:24

标签: python python-dataclasses

我遇到一种情况,我需要将变量a,b和c一起存储在一个数据类中,其中c = f(a,b)和a,b可以被突变。打印对象时,我需要c与a和b一起显示,并且c不能更改,除非通过更改ab。 我觉得最好的方法是让c是使用属性创建的值。

我尝试使用的最小示例:

@dataclass
class Demo:
    a:int
    b:int
    c:int = field(init=False)

    @property
    def c(self) -> int:
        return self.a * self.b

但是,调用此命令将导致AttributeError: can't set attribute。我怀疑代码尝试将c设置为field(init=False),但由于c没有设置器而失败。

我考虑过的选项(不计算我尝试过的使代码以不同方式崩溃的替代方法):

  • 没有初始化的c
    • 在repr,字段等中不显示c,这会导致进一步的问题
  • 具有一个@c.setter不执行任何操作的
    • 这在技术上是可行的,但涉及编写看似无济于事的样板代码。
    • 具有一个函数,该函数永远不会被我编写的任何代码显式调用,但会使程序中断被删除,这很危险而且很不干净。
    • 我还希望程序崩溃,而不是在有人尝试直接修改c的情况下不执行任何操作。
  • 如上所述,但是如果呼叫者不是sys._getframe().f_back.f_code,则在设置器中使用__init__引发错误(尾注中的功能)
    • 仅是Cpython实现细节。
    • 使用标记为私有的功能通常是个坏主意。
  • 使Demo不可变,在c中创建__post_init__,并使用dataclasses.replace修改a和b。
    • 这是在真空中工作的,但是Demo的其他用法(嗯,实际的东西,不是最小的例子)要求它是可变的。

尾注:

    @c.setter
    def c(self, val):
        caller = sys._getframe().f_back.f_code.co_name
        if caller != '__init__':
            raise ValueError # or some other error

2 个答案:

答案 0 :(得分:2)

如果您希望c是一个计算属性,则完全取出c字段并保留该属性:

@dataclass
class Demo:
    a:int
    b:int

    @property
    def c(self) -> int:
        return self.a * self.b

字段用于存储数据,c不应该。

如果您希望c显示在repr中,则必须自己编写__repr__方法。如果以后的代码需要c才能显示在fields中,那么您可能需要在更深的层次上重新编写代码,并可能切换到dataclasses以外的地方。

答案 1 :(得分:1)

您的方法c会覆盖您的字段c-原始的field声明(包括init=False)都将丢失。

尝试更改顺序并使用default=参数。

from dataclasses import dataclass, field


@dataclass
class Demo:
    @property
    def c(self) -> int:
        return self.a * self.b

    a: int
    b: int
    c: int = field(init=False, default=c)


d = Demo(5, 10)
print(d)
d.a = 10
print(d)
d.c = 4  # generates an AttributeError

Python 3.8.0的输出:

Demo(a=5, b=10, c=50)
Demo(a=10, b=10, c=100)
Traceback (most recent call last):
...
  File "/home/rob/src/plot/dc.py", line 20, in <module>
    d.c = 4  # generates an AttributeError
AttributeError: can't set attribute