我正在尝试构建一个定义架构的@dataclass
,但实际上并未使用给定成员实例化。 (基本上,我出于其他目的劫持了方便的@dataclass
语法)。这几乎可以满足我的要求:
@dataclass(frozen=True, init=False)
class Tricky:
thing1: int
thing2: str
def __init__(self, thing3):
self.thing3 = thing3
但是我在FrozenInstanceError
方法中得到了一个__init__
:
dataclasses.FrozenInstanceError: cannot assign to field 'thing3'
我需要frozen=True
(用于散列)。有什么方法可以在冻结的__init__
上的@dataclass
中设置自定义属性?
答案 0 :(得分:3)
我需要
frozen=True
(出于哈希性考虑)。
没有严格的要求冻结类以使其具有可哈希性。您可以选择不从代码中的任何位置更改属性,而设置unsafe_hash=True
。
但是,您应该真正将thing3
声明为字段,并且不要使用自定义的__init__
:
from dataclasses import dataclass, field
from typing import Any
@dataclass(unsafe_hash=True)
class Tricky:
thing1: int = field(init=False)
thing2: str = field(init=False)
thing3: Any
def __post_init__(self):
self.thing1 = 42
self.thing2 = 'foo'
此处thing1
和thing2
设置了init=False
,因此它们不会传递给__init__
方法。然后,您可以使用__post_init__()
方法设置它们。
请注意,这现在要求您不冻结类,否则就不能在自定义的{{1中设置thing1
和thing2
}},而不是__init__
。
演示:
__post_init__
如果您只需要一个模式定义,则根本不需要数据类。您可以从任何类获得类注释; raw annotations或typing.get_type_hints()
。
答案 1 :(得分:2)
问题是the default __init__
implementation uses object.__setattr__()
with frozen classes并通过提供您自己的实现,您也必须使用它,这会使您的代码很hacky:
@dataclass(frozen=True, init=False)
class Tricky:
thing1: int
thing2: str
def __init__(self, thing3):
object.__setattr__(self, "thing3", thing3)
不幸的是,python没有提供使用默认实现的方法,因此我们不能简单地做类似的事情:
@dataclass(frozen=True, init=False)
class Tricky:
thing1: int
thing2: str
def __init__(self, thing3, **kwargs):
self.__default_init__(DoSomething(thing3), **kwargs)
但是,通过我们可以很容易地实现该行为:
def dataclass_with_default_init(_cls=None, *args, **kwargs):
def wrap(cls):
# Save the current __init__ and remove it so dataclass will
# create the default __init__.
user_init = getattr(cls, "__init__")
delattr(cls, "__init__")
# let dataclass process our class.
result = dataclass(cls, *args, **kwargs)
# Restore the user's __init__ save the default init to __default_init__.
setattr(result, "__default_init__", result.__init__)
setattr(result, "__init__", user_init)
# Just in case that dataclass will return a new instance,
# (currently, does not happen), restore cls's __init__.
if result is not cls:
setattr(cls, "__init__", user_init)
return result
# Support both dataclass_with_default_init() and dataclass_with_default_init
if _cls is None:
return wrap
else:
return wrap(_cls)
然后
@dataclass_with_default_init(frozen=True)
class DataClass:
value: int
def __init__(self, value: str):
# error:
# self.value = int(value)
self.__default_init__(value=int(value))
更新:我打开了this错误,希望在3.9之前实现。
答案 2 :(得分:1)
事实证明数据类没有提供您正在寻找的功能。但是 Attrs 确实:
from attr import attrs, attrib
@attrs(frozen=True)
class Name:
name: str = attrib(converter=str.lower)
类似问题的相同答案:见https://stackoverflow.com/a/64695607/3737651
答案 3 :(得分:0)
@Shmuel H.发布的内容对我不起作用,但仍然引发FrozenInstanceError
。
这对我有用:
我在这里正在做的事情是在init中接受一个值,并检查它是否与strptime
函数中定义的格式兼容,如果是的话,我将为其分配,否则将打印该值。例外
@dataclass(frozen=True)
class Birthday:
value: InitVar[str]
date: datetime = field(init=False)
def __post_init__(self, value: str):
try:
self.__dict__['date'] = datetime.strptime(value, '%d/%m/%Y')
except Exception as e:
print(e)