Python 数据类继承和默认值

Mar*_*røy 19 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
Run Code Online (Sandbox Code Playgroud)

是否有任何通用方法允许您在此继承层次结构中插入具有默认值的属性?例如,以下内容会产生Non-default argument(s) follows default argument(s) defined in 'B'class 错误D

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

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

Python 3.7 数据类中的类继承提出了一种解决方案,您可以在继承层次结构中创建两个独立的分支;一种用于默认值,另一种不使用。这里的缺点是层次结构很快就会被很多类弄乱,但如果没有更好的选择,我想这已经是最好的了。

编辑

我想出了一个meta_dataclass可以进行一些黑客攻击的方法,至少可以解决我的问题。我确信它违反了各种规则,但也许有人可以改进它。

它允许meta_dataclasses层次结构中的任何属性都可以具有默认值。它的灵感来自Python 3.7 数据类中的类继承中提出的解决方案。它有效地创建了两个继承分支,一个具有默认值,一个没有默认值,但是实现这一点所需的额外类的创建被封装在装饰器中mete_dataclass。当前的一个缺点是 ameta_dataclass只能从 other 继承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)

Run Code Online (Sandbox Code Playgroud)

例子

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

插图

下图说明了@meta_dataclass装饰器对单个类执行的操作。

@meta_dataclass 作用的说明

Raf*_*lar 18

这是数据类的一个众所周知的问题,有多种解决方法,但这在 Python 3.10 中得到了非常优雅的解决,这里是解决问题43532 的PR 。

它将按以下方式工作:

from dataclasses import dataclass

@dataclass(kw_only=True)
class Base:
  type: str
  counter: int = 0

@dataclass(kw_only=True)
class Foo(Base):
  id: int
Run Code Online (Sandbox Code Playgroud)

这在以下位置进行了深入解释: https: //medium.com/@aniscampos/python-dataclass-inheritance-finally-686eaf60fbb5


对于最初的问题,C和是将标志设置为的D最低必需类:kw_onlyTrue

@dataclass
class A:
    a: str
    aa: str


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


@dataclass(kw_only=True)
class C(A):
    c: str


@dataclass(kw_only=True)
class D(B, C):
    d: str
Run Code Online (Sandbox Code Playgroud)

尽管在这件事上他们所有人都可以将标志设置为 True。