如何在冻结的数据类自定义 __init__ 方法中设置属性?

Sas*_*lla 11 python python-3.x python-3.7 python-dataclasses

我正在尝试构建一个@dataclass定义架构但实际上并未使用给定成员实例化的模型。(基本上,我@dataclass为了其他目的劫持了方便的语法)。这几乎就是我想要的:

@dataclass(frozen=True, init=False)
class Tricky:
    thing1: int
    thing2: str

    def __init__(self, thing3):
        self.thing3 = thing3
Run Code Online (Sandbox Code Playgroud)

但是我FrozenInstanceError__init__方法中得到了一个:

dataclasses.FrozenInstanceError: cannot assign to field 'thing3'
Run Code Online (Sandbox Code Playgroud)

我需要frozen=True(为了哈希)。有什么方法可以在__init__冻结上设置自定义属性@dataclass吗?

Shm*_* H. 12

问题是默认__init__实现object.__setattr__()与冻结类一起使用,并且通过提供您自己的实现,您也必须使用它,这会使您的代码非常笨拙:

@dataclass(frozen=True, init=False)
class Tricky:
    thing1: int
    thing2: str

    def __init__(self, thing3):
        object.__setattr__(self, "thing3", thing3)
Run Code Online (Sandbox Code Playgroud)

不幸的是,python 没有提供使用默认实现的方法,因此我们不能简单地执行以下操作:

@dataclass(frozen=True, init=False)
class Tricky:
    thing1: int
    thing2: str

    def __init__(self, thing3, **kwargs):
        self.__default_init__(DoSomething(thing3), **kwargs)
Run Code Online (Sandbox Code Playgroud)

但是,通过我们可以很容易地实现该行为:

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)
Run Code Online (Sandbox Code Playgroud)

进而

@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))
Run Code Online (Sandbox Code Playgroud)

更新:我打开了这个错误,我希望在 3.9 之前实现它。

  • 您好,为什么您将您的解决方案表示为“相当黑客”?(`object.__setattr__(self, "thing3", thing3)`) 它工作正常并且紧凑。称“对象”是一种不好的做法吗? (4认同)
  • 不幸的是,链接的问题被关闭为“无法修复”。 (2认同)

Tim*_*mmm 10

这是一个更简单的选项 - 只需添加一个静态make函数:

@dataclass(frozen=True)
class Tricky:
    thing1: str
    thing2: int
    thing3: bool

    @classmethod
    def make(cls, whatever: str, you: bool, want: float):
        return cls(whatever + "..", you * 4, want > 5)

x = Tricky.make("foo", false, 3)
Run Code Online (Sandbox Code Playgroud)

根据您的make方法的用途,遵循 Rust 的命名约定 - 可能是个好主意from_foo()。例如

@dataclass(frozen=True)
class Coord:
    lat: float
    lon: float

    @classmethod
    def from_os_grid_reference(cls, x: int, y: int):
        return cls(...)

    @classmethod
    def from_gps_nema_string(cls, nema_string: str):
        return cls(...)
Run Code Online (Sandbox Code Playgroud)


Mar*_*ers 7

我需要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'
Run Code Online (Sandbox Code Playgroud)

此处thing1thing2init=False设置,因此不会将它们传递给__init__方法。然后在__post_init__()方法中设置它们。

请注意,这现在要求您不要冻结该类,否则您无法设置thing1and thing2,不在自定义中__init__也不在__post_init__.

演示:

>>> Tricky('bar')
Tricky(thing1=42, thing2='foo', thing3='bar')
>>> hash(Tricky('bar'))
-3702476386127038381
Run Code Online (Sandbox Code Playgroud)

如果您想要的只是模式定义,则根本不需要数据类。您可以从任何类中获取类注释;无论是作为原始注释还是使用typing.get_type_hints().

  • 只是不要使用数据类。该符号并非特定于该库。 (2认同)
  • 只需访问注释:`__annotations__`。或者使用“typing.get_type_hints()”的类型提示 (2认同)

Pie*_*aar 5

事实证明,数据类没有提供您正在寻找的功能。然而Attrs确实:

from attr import attrs, attrib


@attrs(frozen=True)
class Name:
    name: str = attrib(converter=str.lower)
Run Code Online (Sandbox Code Playgroud)

类似问题的相同答案:参见/sf/answers/4528692521/