Классы данных Python наследуют часть атрибутов родительского класса данных

Я построил множественную базу dataclass. Теперь я хочу создать дочерний dataclass, унаследованный от этих базовых классов, но может унаследовать часть атрибутов, определенных в некоторых базовых классах. Примеры могут быть:

import dataclasses

@dataclasses.dataclass
class A:
    a: int = None
    b: float = None
    c: str = None

@dataclasses.dataclass
class B:
    d: int = None
    b: float = 3.5

@dataclasses.dataclass
class C:
    e: int = None
    f: float = 3.5
    g: int = None

@dataclasses.dataclass
class D(A, B):
    def __post_init__(self):
        for _field, _field_property in C.__dataclass_fields__.items():
            if _field != "g":
                setattr(self, _field, _field_property.default)

А именно, я хочу создать дочерний класс D, наследующий A и B, а также атрибуты в C, кроме g. Проверка дочернего класса D

>>> D.__dataclass_fields__.keys() # got dict_keys(['d', 'b', 'a', 'c'])
>>> d = D(a=4, b=2, c=5, d=3.4, e=2.1, g=55)
Traceback (most recent call last):
  File "<pyshell#77>", line 1, in <module>
    d = D(a=4, b=2, c=5, d=3.4, e=2.1, g=55)
TypeError: __init__() got an unexpected keyword argument 'e'

А также

>>> D.__dict__.keys()
dict_keys(['__module__', '__post_init__', '__doc__', '__dataclass_params__', '__dataclass_fields__', '__init__', '__repr__', '__eq__', '__hash__'])

Когда я изменил __post_init__ на __init__ и использовал super().__init__() для наследования, по-прежнему не могу атрибуты из класса C и потерять преимущество dataclass, т. Е.

>>> @dataclasses.dataclass
class D(A, B):
    def __init__(self):
        super().__init__()
        for _field, _field_property in C.__dataclass_fields__.items():
            if _field != "g":
                setattr(self, _field, _field_property.default)

И беги

>>> d = D(a=4, b=2, c=5, d=3.4, e=2.1, g=55)
Traceback (most recent call last):
  File "<pyshell#81>", line 1, in <module>
    d = D(a=4, b=2, c=5, d=3.4, e=2.1, g=55)
TypeError: __init__() got an unexpected keyword argument 'a'

Что я должен делать?


person Elkan    schedule 09.03.2020    source источник
comment
Я тоже столкнулся с этим и не думаю, что dataclasses действительно поддерживает наследование.   -  person AKX    schedule 09.03.2020
comment
Создайте класс C', который похож на C без атрибута g, C станет подклассом C', добавив новый атрибут g, а ваш D наследует A,B, C'. Классы данных работают в время создания класса, __post_init__ работают в время создания экземпляра, т.е. слишком поздно для изменения определения __init__   -  person Giacomo Alzetta    schedule 09.03.2020
comment
Хорошо, что вы нашли рабочее решение, но вам также следует подумать об использовании композиции вместо наследования. В случае, если вы представляете, похоже, что классу D может быть лучше иметь экземпляры A, B и, возможно, C_part вместо их наследования.   -  person Arne    schedule 09.03.2020


Ответы (1)


Подобно тому, что было предложено @GiacomoAlzetta, я внезапно высказал эту идею, используя dataclasses.make_dataclass, то есть создать копию C, но исключая атрибут g, т.е.

<<< C_part = dataclasses.make_dataclass("C_part", [(_field, _field_property.type, _field_property.default) for _field, _field_property in C.__dataclass_fields__.items() if _field != "g"])

Таким образом, у меня есть

>>> C_part.__dataclass_fields__.keys()  # dict_keys(['e', 'f'])

Тогда D можно получить

>>> @dataclasses.dataclass
class D(A, B, C_part):
    pass

>>> d = D(a=4, b=2, c=5, d=3.4, e=2.1, f=55)
>>> d
D(e=2.1, f=55, d=3.4, b=2, a=4, c=5)
person Elkan    schedule 09.03.2020