我正在尝试创建冻结的数据类,但在设置中的值时遇到问题__post_init__。有没有办法从设置基于值的字段值init param在dataclass使用时frozen=True设置?
RANKS = '2,3,4,5,6,7,8,9,10,J,Q,K,A'.split(',')
SUITS = 'H,D,C,S'.split(',')
@dataclass(order=True, frozen=True)
class Card:
rank: str = field(compare=False)
suit: str = field(compare=False)
value: int = field(init=False)
def __post_init__(self):
self.value = RANKS.index(self.rank) + 1
def __add__(self, other):
if isinstance(other, Card):
return self.value + other.value
return self.value + other
def __str__(self):
return f'{self.rank} of {self.suit}'
Run Code Online (Sandbox Code Playgroud)
这就是痕迹
File "C:/Users/user/.PyCharm2018.3/config/scratches/scratch_5.py", line 17, in __post_init__
self.value = RANKS.index(self.rank) + 1
File "<string>", line 3, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'value'
Run Code Online (Sandbox Code Playgroud)
use*_*ica 18
使用生成的__init__方法做的同样的事情:object.__setattr__。
def __post_init__(self):
object.__setattr__(self, 'value', RANKS.index(self.rank) + 1)
Run Code Online (Sandbox Code Playgroud)
Pet*_*ler 14
这是 @Anna Giasson 答案的简化版本。
冻结数据类与functools模块中的缓存配合良好 。dataclass您可以定义一个带注释的方法,而不是使用字段@functools.cached_property,该方法仅在第一次查找属性时进行评估。这是原始示例的最小版本:
from dataclasses import dataclass
import functools
@dataclass(frozen=True)
class Card:
rank: str
@functools.cached_property
def value(self):
# just for demonstration:
# this gets printed only once per Card instance
print("Evaluate value")
return len(self.rank)
card = Card(rank="foo")
assert card.value == "foo"
assert card.value == "foo"
Run Code Online (Sandbox Code Playgroud)
在实践中,如果评估成本低廉,您也可以使用非缓存@property装饰器。
Max*_*ner 13
我在几乎所有类中使用的解决方案是将附加构造函数定义为类方法。
根据给定的示例,可以将其重写如下:
@dataclass(order=True, frozen=True)
class Card:
rank: str = field(compare=False)
suit: str = field(compare=False)
value: int
def __post_init__(self) -> None:
if not is_valid_rank(self.rank):
raise ValueError(f"Rank {self.rank} of Card is invalid!")
@classmethod
def from_rank_and_suite(cls, rank: str, suit: str) -> "Card":
value = RANKS.index(self.rank) + 1
return cls(rank=rank, suit=suit, value=value)
Run Code Online (Sandbox Code Playgroud)
这样一来,人们就拥有了所需的所有自由,而不必求助于__setattr__黑客,也不必放弃所需的严格性,例如frozen=True.
不应改变冻结的物体。但有时可能会出现这种需要。接受的答案非常适合这一点。这是解决此问题的另一种方法:返回具有更改后的值的新实例。对于某些情况来说,这可能有点过分了,但它是一种选择。
from copy import deepcopy
@dataclass(frozen=True)
class A:
a: str = ''
b: int = 0
def mutate(self, **options):
new_config = deepcopy(self.__dict__)
# some validation here
new_config.update(options)
return self.__class__(**new_config)
Run Code Online (Sandbox Code Playgroud)
如果要设置全部或多个值,可以__init__在 内部再次调用__post_init__。虽然用例不多。
以下示例并不实用,仅用于演示可能性。
from dataclasses import dataclass, InitVar
@dataclass(frozen=True)
class A:
a: str = ''
b: int = 0
config: InitVar[dict] = None
def __post_init__(self, config: dict):
if config:
self.__init__(**config)
Run Code Online (Sandbox Code Playgroud)
以下调用
A(config={'a':'a', 'b':1})
Run Code Online (Sandbox Code Playgroud)
将产生
A(a='a', b=1)
Run Code Online (Sandbox Code Playgroud)
没有抛出错误。这是在 python 3.7 和 3.9 上测试的。
当然,你可以直接使用 构造A(a='hi', b=1),但也许还有其他用途,例如从 json 文件加载配置。
A(config={'a':'a', 'b':1, 'config':{'a':'b'}})
Run Code Online (Sandbox Code Playgroud)
将产生
A(a='b', b=1)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1881 次 |
| 最近记录: |