управлять порядком инициализации, когда класс данных Python наследует класс - PullRequest
0 голосов
/ 28 февраля 2019

То, что я знаю
Класс данных Python допускает наследование либо с классом данных, либо с классом.В лучшей практике (а также в некоторых языках), когда мы делаем наследование, инициализация должна быть вызвана первой.В Python это:

def __init__(self):
    super().__init__()
    ...

Что я делаю
Поскольку класс данных был представлен в Python 3.7, я подумываю заменить весь мой класс на класс данных.С dataclass одна из его целей - генерировать __init__ для вас.Это не очень хорошо, когда класс данных должен наследовать класс - например:

class Base:
    def __init__(self):
        self.a = 1

@dataclass
class Child(Base):
    a:int
    def __post_init__(self):
        super().__init__() 

В чем моя проблема
Проблема в том, что мы должны поместить вызов супер инициализации внутри __post_init__ который на самом деле называется после класса данных init .
Недостатком является то, что мы теряем договорный договор, а беспорядок инициализации приводит к тому, что мы не можем переопределить атрибуты суперклассов.

Это можно решить с помощью концепции __pre_init__.Я прочитал документ и не вижу там ничего общего с этой концепцией.Я что-то упустил?

Ответы [ 3 ]

0 голосов
/ 28 февраля 2019

На самом деле есть один метод, который вызывается до __init__: это __new__.Так что вы можете сделать такой трюк: позвоните Base.__init__ в Child.__new__.Не могу сказать, что это хорошее решение, но если вы заинтересованы, вот рабочий пример:

class Base:
    def __init__(self, a):
        self.a = 1


@dataclass
class Child(Base):
    a: int

    def __new__(cls, *args, **kwargs):
        obj = object.__new__(cls)
        Base.__init__(obj, *args, **kwargs)
        return obj


c = Child(a=3)
print(c.a)  # 3, not 1, because Child.__init__ overrides a
0 голосов
/ 11 марта 2019

В наилучшей практике [...], когда мы делаем наследование, инициализация должна быть вызвана первой.

Это разумная рекомендация, которой необходимо следовать, но в конкретном случаеиз классов данных, это не имеет никакого смысла.

Есть две причины для вызова конструктора родителя: 1) для создания экземпляров аргументов, которые должны быть обработаны конструктором родителя, и 2) для запуска любой логики в родительском конструкторе, которая должна произойти до реализации.

Классы данных уже обрабатывают первый для нас:

 @dataclass
class A:
    var_1: str

@dataclass
class B(A):
    var_2: str

print(B(var_1='a', var_2='b'))  # prints: B(var_1='a', var_2='b')
# 'var_a' got handled without us needing to do anything

А второй класс не относится к классам данных.Другие классы могут делать разные странные вещи в своем конструкторе, но классы данных делают только одно: они присваивают входные аргументы своим атрибутам.Если им нужно сделать что-то еще (что не может быть обработано __post_init__), вы можете написать класс, который не должен быть классом данных.

0 голосов
/ 28 февраля 2019

как насчет:

from dataclasses import dataclass


class Base:
    def __init__(self, a=1):
        self.a = a


@dataclass
class Child(Base):

    def __post_init__(self):
        super().__init__()


ch = Child()
...