在 python 数据类中使用 属性 getter 而没有 setter 的 pythonic 方法是什么?
What is a pythonic way to have property getters without setters with python dataclasses?
我有一种情况需要将变量 a、b 和 c 一起存储在一个数据类中,其中 c = f(a,b)
和 a、b 可以改变。我需要在打印对象时 c
与 a 和 b 一起显示,并且 c
不能更改,除非通过更改 a
或 b
。
我觉得最好的方法是让 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 没有 setter.
我考虑过的选项(不包括我尝试过的导致代码以不同方式崩溃的替代方案):
- 初始化中没有
c
- 不在 repr、字段等中显示 c,这会导致进一步的问题
- 有一个什么都不做的
@c.setter
- 这在技术上可行,但涉及编写看似什么都不做的样板代码。
- 有一个函数永远不会被我编写的任何代码显式调用,但会使程序中断被删除,感觉很危险而且不干净。
- 如果有人试图直接修改 c,我也希望程序崩溃而不是静静地什么也不做。
- 同上,但如果调用者不是
__init__
(函数在尾注处),则在 setter 中使用 sys._getframe().f_back.f_code
以引发错误
- 仅为 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
如果您希望 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
.
以外的代码。
您的方法 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
我有一种情况需要将变量 a、b 和 c 一起存储在一个数据类中,其中 c = f(a,b)
和 a、b 可以改变。我需要在打印对象时 c
与 a 和 b 一起显示,并且 c
不能更改,除非通过更改 a
或 b
。
我觉得最好的方法是让 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 没有 setter.
我考虑过的选项(不包括我尝试过的导致代码以不同方式崩溃的替代方案):
- 初始化中没有
c
- 不在 repr、字段等中显示 c,这会导致进一步的问题
- 有一个什么都不做的
@c.setter
- 这在技术上可行,但涉及编写看似什么都不做的样板代码。
- 有一个函数永远不会被我编写的任何代码显式调用,但会使程序中断被删除,感觉很危险而且不干净。
- 如果有人试图直接修改 c,我也希望程序崩溃而不是静静地什么也不做。
- 同上,但如果调用者不是
__init__
(函数在尾注处),则在 setter 中使用sys._getframe().f_back.f_code
以引发错误- 仅为 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
如果您希望 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
.
您的方法 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