Python数据类继承和默认值

2024-05-23 19:02:45 发布

您现在位置:Python中文网/ 问答频道 /正文

给定以下python数据类的层次结构:

@dataclass
class A:
    a: str
    aa: str


@dataclass
class B(A):
    b: str


@dataclass
class C(A):
    c: str


@dataclass
class D(B, C):
    d: str

是否有任何常规方法允许您在此继承层次结构中插入具有默认值的属性?例如,下面为类D生成一个Non-default argument(s) follows default argument(s) defined in 'B'错误

@dataclass
class A:
    a: str
    aa: str


@dataclass
class B(A):
    b: str = 'b'  # <-- default value


@dataclass
class C(A):
    c: str


@dataclass
class D(B, C):
    d: str

我认为一种解决方案是为层次结构中的所有属性提供一个默认值None,并添加一个__post_init__()验证,如果任何属性都是None,则会引发一些异常,但这似乎不正确,而且层次结构中的每个数据类都需要它

Class inheritance in Python 3.7 dataclasses提出了一个解决方案,其中创建继承层次结构的两个独立分支;一个用于默认值,另一个不用于默认值。这里的缺点是层次结构很快就会被很多类弄乱,但是如果没有更好的选项,我想这是最好的了

编辑

我想出了一个meta_dataclass方法,它至少可以解决我的问题。我相信它打破了各种规则,但也许有人可以改进它

它允许meta_dataclasses层次结构,其中层次结构中的任何属性都可以有默认值。它是由Class inheritance in Python 3.7 dataclasses中提出的解决方案推动的。它有效地创建了两个继承分支,一个带有默认值,一个没有,但是实现这一点所需的额外类的创建被封装在mete_dataclassdecorator中。当前的缺点是meta_dataclass只能从其他meta_dataclasses继承

完整代码

from dataclasses import make_dataclass, MISSING, field


class Dataclass:
    pass


class Declarations:
    pass


class Definitions:
    pass


def meta_dataclass(cls=None, /, *, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False):
    def wrap(cls):
        declaration_bases = []
        definition_bases = []

        for base in cls.__bases__:
            if issubclass(base, Dataclass):
                declaration_bases += [c for c in base.__bases__ if issubclass(c, Declarations)]
                definition_bases += [c for c in base.__bases__ if issubclass(c, Definitions)]
            elif len(cls.__bases__) == 1 and base != object:
                raise ValueError(f'meta_dataclasses can only inherit from other meta_dataclasses. '
                                 f'{cls} inherits from {base}')

        declaration_bases.append(Declarations)
        definition_bases.append(Definitions)

        fields = []
        if hasattr(cls, '__annotations__'):
            for field_name, field_type in cls.__annotations__.items():
                f = field(default=cls.__dict__[field_name]) if field_name in cls.__dict__ else field()
                fields.append((field_name, field_type, f))

        declarations = make_dataclass(cls_name=f'{cls.__name__}_Declarations',
                                      bases=tuple(declaration_bases),
                                      fields=[f for f in fields if isinstance(f[2].default, type(MISSING))],
                                      init=init, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen)

        definitions = make_dataclass(cls_name=f'{cls.__name__}_Definitions',
                                     bases=tuple(definition_bases),
                                     fields=[f for f in fields if not isinstance(f[2].default, type(MISSING))],
                                     init=init, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen)

        cls_wrapper = make_dataclass(cls_name=cls.__name__, fields=[], bases=(Dataclass, definitions, declarations),
                                     namespace=cls.__dict__, init=init, repr=repr, eq=eq, order=order,
                                     unsafe_hash=unsafe_hash, frozen=frozen)

        return cls_wrapper

    if cls is None:
        return wrap
    else:
        return wrap(cls)

示例

@meta_dataclass
class A:
    a: str
    aa: str

@meta_dataclass
class B(A):
    b: str = 'b'

@meta_dataclass
class C(A):
    c: str

@meta_dataclass
class D(B, C):
    d: str


>>> help(D)
Help on class D in module types:

class D(__main__.Dataclass, D_Definitions, D_Declarations)
 |  D(a: str, aa: str, c: str, d: str, b: str = 'b') -> None
 |  
 |  D(a: str, aa: str, c: str, d: str, b: str = 'b')
 |  
 |  Method resolution order:
 |      D
 |      __main__.Dataclass
 |      D_Definitions
 |      B_Definitions
 |      C_Definitions
 |      A_Definitions
 |      __main__.Definitions
 |      D_Declarations
 |      B_Declarations
 |      C_Declarations
 |      A_Declarations
 |      __main__.Declarations
 |      builtins.object

插图

下图说明了@meta_dataclass装饰器对单个类所做的操作

Illustration of what @meta_dataclass does


Tags: nameinfieldif层次结构initordermeta