如何正确继承数据类并重用基类实例
考虑以下内容:
@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
,同时保留所有现有的值。通常情况下,按照我的理解,如果我想要一个Derived1
或Derived2
类的实例,我必须先初始化这个实例,然后手动在派生类的__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
}