简体   繁体   English

什么是使用 python 数据类的没有设置器的属性获取器的 pythonic 方式?

[英]What is a pythonic way to have property getters without setters with python dataclasses?

I have a situation where I need to store variables a,b, and c together in a dataclass, where c = f(a,b) and a,b can be mutated.我有一种情况,我需要将变量 a、b 和 c 一起存储在一个数据类中,其中c = f(a,b)和 a,b 可以突变。 I need c to be displayed along with a and b when printing the object, and c cannot be changed except through changing a or b .打印 object 时,我需要c与 a 和 b 一起显示,并且c不能更改,除非通过更改ab I felt the best way to do this was to have c be a value created with a property.我觉得做到这一点的最好方法是让c成为使用属性创建的值。

Minimal example of what I tried to use:我尝试使用的最小示例:

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

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

However, calling this results in AttributeError: can't set attribute .但是,调用它会导致AttributeError: can't set attribute I suspect the code is trying to set c to field(init=False) , and failing because c doesn't have a setter.我怀疑代码试图将 c 设置为field(init=False) ,但由于 c 没有设置器而失败。

Options I've considered (not counting alternatives I tried that makes the code crash in a different manner):我考虑过的选项(不包括我尝试过的使代码以不同方式崩溃的替代方案):

  • not having c in init初始化中没有c
    • doesn't display c in repr, fields, etc. , which causes further issues down the line在 repr、字段等中不显示 c,这会导致进一步的问题
  • having a @c.setter that does nothing有一个什么都不做的@c.setter
    • this technically works, but involves writing boilerplate code that seemingly does nothing.这在技术上是可行的,但涉及编写看似什么都不做的样板代码。
    • having a function that never gets called explicitly by any code I write but makes the program break is removed feels dangerous and unclean.拥有一个 function 永远不会被我编写的任何代码显式调用,但会使程序中断被删除感觉危险和不干净。
    • I also want the program to crash instead of silently doing nothing if someone tries to modify c directly.如果有人试图直接修改 c,我还希望程序崩溃而不是默默地什么都不做。
  • as above, but use sys._getframe().f_back.f_code in the setter to raise an error if the caller is not __init__ (function at endnote)如上所述,但如果调用者不是__init__ (尾注中的函数),请在 setter 中使用sys._getframe().f_back.f_code引发错误
    • Is a Cpython implementation detail only.只是一个 Cpython 实现细节。
    • Using functions that are marked as private is a bad idea, generally speaking.一般来说,使用标记为私有的函数是个坏主意。
  • have Demo be immutable, have c be created in __post_init__ , and use dataclasses.replace to modify a and b.让 Demo 不可变,在c中创建__post_init__ ,并使用dataclasses.replace修改 a 和 b。
    • this works in a vacuum, but other uses of Demo (well, the actual thing, not the minimal example) require it to be mutable.这在真空中工作,但Demo的其他用途(嗯,实际的东西,而不是最小的例子)要求它是可变的。

endnote:尾注:

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

If you want c to be a computed property, then take out the c field entirely and just leave the property:如果您希望c成为计算属性,则完全取出c字段并保留该属性:

@dataclass
class Demo:
    a:int
    b:int

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

Fields are for stored data, which c should not be.字段用于存储数据, c不应该是。

If you want c to show up in repr , you'll have to write the __repr__ method yourself.如果您希望c出现在repr中,您必须自己编写__repr__方法。 If later code needs c to show up in fields , then you probably need to rework your code at a deeper level, and possibly switch to something other than dataclasses .如果以后的代码需要c显示在fields中,那么您可能需要在更深层次上重新编写代码,并可能切换到dataclasses以外的东西。

Your method c overwrites your field c -- the original field declaration is lost, including the init=False .您的方法c覆盖您的字段c - 原始field声明丢失,包括init=False

Try changing the order and using the default= parameter.尝试更改顺序并使用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

Output from Python 3.8.0: Output 来自 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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM