如何正确继承数据类并重用基类实例

-1 投票
1 回答
38 浏览
提问于 2025-04-13 00:36

考虑以下内容:

@dataclass
class Base:
  prop1: str
  prop2: str


@dataclass
class Derived1(Base):
  isValid: bool = self.prop2.casefold() == 'valid'

@dataclass
class Derived2(Base):
  isOpen: bool = self.prop1.casefold() == 'open'
  isShared :bool


在这个例子中,基类(baseclass)只有两个属性,但假设它有773个属性,仅仅是为了说明问题。某个地方,有个东西返回了一个Base的实例。现在,你可能想把它提升为Derived1,同时保留所有现有的值。通常情况下,按照我的理解,如果我想要一个Derived1Derived2类的实例,我必须先初始化这个实例,然后手动在派生类的__init____post_init__中为所有773个属性逐一赋值。这简直是疯狂。经过一些研究,我发现了一个很好的建议来解决这个问题。相反,我使用普通类,并通过__dict__的技巧从现有的基类实例更新属性:

class Derived(Base):
    isOpen: bool = False

    def __init__(self, base: Base):
        self.__dict__.update(base.__dict__)
        self.isOpen = (str(base.currStatus).casefold() == 'open')

虽然这看起来运行得很好,但我一直收到pylint的错误提示,提示我需要使用super。我对此感到困惑 - 我在这里真的需要super吗?问题是,使用super后,我又回到了最初的状态,必须手动重新初始化700多个属性……这正是我想要避免的。

这一切看起来非常愚蠢 - 我们在讨论的是Python,我很容易想象在处理类时会出现这个问题(而其他一些语言早在30年前就解决了这个问题)。

1 个回答

0

使用 asdict 并包含字典解包的要求。下面的混合类已经具备了这个功能,还有更多其他的功能。

# mixins.py
from __future__  import annotations
from dataclasses import asdict
from typing      import Any, Iterable
import json


""" Dataclass_mi: dataclass mixin for dictionary behavior
  supports:
    *) .keys(), .values(), .items()
    *) dictionary unpacking
    *) pretty-print self as json
    *) return self as dict
    *) reassign any/all fields in one call
      +) optional __post_init__ call after reassignment
"""
class Dataclass_mi:
  @property
  def asdict(self) -> dict:
    return asdict(self)
    
  def items(self) -> Iterable:
    return asdict(self).items()
    
  def values(self) -> Iterable:
    return asdict(self).values()
      
  # required for dictionary unpacking
  def keys(self) -> Iterable:
    return asdict(self).keys()
  
  # required for dictionary unpacking  
  # an error will be raised if this key does not exist
  def __getitem__(self, key:str) -> Any:
    return getattr(self, key)
  
  # pretty-print self as json
  def __str__(self) -> str:
    return json.dumps(asdict(self), indent=4, default=str)
  
  # reassign fields from kwargs and optionally call __post_init__
  def __call__(self, initvars:dict|None=None, **kwargs) -> Dataclass_mi:
    # if key exists, set value. otherwise, ignore.
    for key, value in kwargs.items():
      if hasattr(self, key):
        setattr(self, key, value)
        
    # if you want __post_init__ to be called, but it doesn't accept arguments -
    # pass initvars as an empty dict
    if initvars is not None: 
      self.__post_init__(**initvars)
        
    # let this be an inline method        
    return self
from dataclasses import dataclass
from mixins import Dataclass_mi
   
@dataclass
class Base(Dataclass_mi):
  prop1: str
  prop2: str


@dataclass
class Derived1(Base):
  isValid: bool = False

  def __post_init__(self) -> None:
    self.isValid = self.prop2.lower() == 'valid'


@dataclass
class Derived2(Base):
  isShared: bool 
  isOpen  : bool = False
  
  def __post_init__(self) -> None:
    self.isOpen = self.prop1.lower() == 'open'


用法
base = Base('open', 'valid')

# init
derived = Derived2(**base, isShared=False)

print(derived)

# customize
derived({}, prop1='closed')

print(derived)
输出
{
    "prop1": "open",
    "prop2": "valid",
    "isShared": false,
    "isOpen": true
}
{
    "prop1": "closed",
    "prop2": "valid",
    "isShared": false,
    "isOpen": false
}

撰写回答