从数据类中的自定义构造函数调用生成的 __init__ 作为默认值

Cod*_*ope 6 python python-dataclasses

是否可以从 中受益dataclasses.field,特别是对于默认值,但使用自定义构造函数?我知道@dataclass注释在生成的中设置默认值__init__,如果我替换它,就不会再这样做了。那么,是否可以替换生成的__init__,并仍然在内部调用它?

@dataclass
class A:
    l: list[int] = field(default_factory=list)
    i: int = field(default=0)
        
    def __init__(self, a: Optional[int]): # completely different args than instance attributes
        self.call_dataclass_generated_init() # call generated init to set defaults
        if a is not None: # custom settings of attributes
            i = 2*a
Run Code Online (Sandbox Code Playgroud)

解决方法是定义__new__而不是覆盖__init__,但我更愿意避免这种情况。

  • 这个问题非常接近,但答案仅解决作为代码示例给出的特定用例。另外,我不想使用,__post_init__因为我需要使用__setattr__这是静态类型检查的问题,并且它无助于调整__init__无论如何都会采用的参数。

  • 我也不想使用类方法,我真的希望调用者使用自定义构造函数。

  • 这一个也很接近,但它只是解释为什么新的构造函数替换生成的构造函数,而不是关于如何仍然调用后者(还有一个回复建议使用 Pydantic,但我不想子类化BaseModel,因为这会弄乱我的继承权)。

因此,简而言之,我希望受益于dataclass具有属性默认值的功能,而不需要繁琐的解决方法。请注意,原始默认值对我来说不是一个选项,因为它设置类属性:

class B:
    a: int = 0 # this will create B.a class attribute, and vars(B()) will be empty
    l: list[int] = [] # worse, a mutable object will be shared between instances
Run Code Online (Sandbox Code Playgroud)

jsb*_*eno 3

据我所知,更干净的方法是有一个替代的类方法用作构造函数:这样,数据类将完全按照预期工作,您可以这样做:

from dataclasses import dataclass, field
from typing import Optional


@dataclass
class A:
    l: list[int] = field(default_factory=list)
    i: int = field(default=0)
     
    @classmethod
    def new(cls, a: Optional[int]=0): # completely different args than instance attributes
        # creates a new instance with default values:
        instance = cls()
        # if one wants to have more control over the instance creation, it is possible to call __new__ and __init__ manually:
        # instance = cls.__new__(cls)
        # instance.__init__()
        if a is not None: # custom settings of attributes
            i = 2*a
            
        return instance
Run Code Online (Sandbox Code Playgroud)

但是,如果您不需要显式构造函数方法,并且确实需要调用 just A(),则可以通过创建一个装饰器来完成,该装饰器将在之后应用@dataclass- 然后它可以移动__init__到另一个名称。唯一的事情是您的自定义__init__必须调用另一个名称,否则@dataclass 将不会创建该方法。

def custom_init(cls):
    cls._dataclass_generated_init = cls.__init__
    cls.__init__ = cls.__custom_init__
    return cls

@custom_init    
@dataclass
class A:
    l: list[int] = field(default_factory=list)
    i: int = field(default=0)
        
    def __custom_init__(self, a: Optional[int]): # completely different args than instance attributes
        self._dataclass_generated_init() # call generated init to set defaults
        if a is not None: # custom settings of attributes
            i = 2*a
            ...
        print("custom init called")
Run Code Online (Sandbox Code Playgroud)

  • 类方法不需要显式调用`__new__`和`__init__`;它只需要像平常一样调用 `cls`:`instance = cls()`。 (2认同)