Python: абстрактный базовый класс '__init __(): инициализация или проверка?

class ABC - это "абстрактный базовый класс". class X является его подклассом.

Там есть какая-то работа, которая должна выполняться в любом подклассе ABC, который легко забыть или сделать неправильно. Я бы хотел, чтобы ABC.__init__() помог поймать такие ошибки:

(1), начиная с этой работы, или (2) проверка его

Это влияет на то, что super().__init__() вызывается в начале или в конце X.__init__().

Здесь приведен упрощенный пример для иллюстрации:

Предположим, что каждый подкласс ABC должен иметь атрибут registry, и он должен быть списком. ABC.__init__() может либо (1) инициализировать registry, либо (2) проверить, что он был правильно создан. Ниже приведен пример кода для каждого подхода.

Подход 1: инициализация в ABC

class ABC:
    def __init__(self):
        self.registry = []

class X:
    def __init__(self):
        super().__init__()
        # populate self.registry here
        ...

Подход 2: подтверждение в ABC

class ABC:
    class InitializationFailure(Exception):
        pass
    def __init__(self):
        try:
            if not isinstance(self.registry, list):
                raise ABC.InitializationError()
        except AttributeError:
            raise ABC.InitializationError()

class X:
    def __init__(self):
        self.registry = []
        # populate self.registry here
        ...
        super().__init__()

Какой лучший дизайн?

Ответы

Ответ 1

Конечно, один предпочитает подход 1 приблизиться к 2 (поскольку подход 2 отводит базу к интерфейсу тега, а не выполняет абстрактную функциональность). Но подход 1 сам по себе не отвечает вашей цели - не дать разработчику подтипа забыть правильно реализовать вызов super(), гарантируя инициализацию.

вы можете захотеть просмотреть шаблон "Factory", чтобы облегчить возможность реализации инициализации подтипов. Рассмотрим:

class AbstractClass(object):
    '''Abstract base class template, implementing factory pattern through 
       use of the __new__() initializer. Factory method supports trivial, 
       argumented, & keyword argument constructors of arbitrary length.'''

   __slots__ = ["baseProperty"]
   '''Slots define [template] abstract class attributes. No instance
       __dict__ will be present unless subclasses create it through 
       implicit attribute definition in __init__() '''

   def __new__(cls, *args, **kwargs):
       '''Factory method for base/subtype creation. Simply creates an
       (new-style class) object instance and sets a base property. '''
       instance = object.__new__(cls)

       instance.baseProperty = "Thingee"
       return instance

Этот базовый класс может быть расширен более тривиально, чем в подходе 1, используя только три (3) строки кодовой санкции, следующим образом:

class Sub(AbstractClass):
   '''Subtype template implements AbstractClass base type and adds
      its own 'foo' attribute. Note (though poor style, that __slots__
      and __dict__ style attributes may be mixed.'''

   def __init__(self):
       '''Subtype initializer. Sets 'foo' attribute. '''
       self.foo = "bar"

Обратите внимание, что хотя мы не называем конструктор суперкласса, baseProperty будет инициализирован:

Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from TestFactory import *
>>> s = Sub()
>>> s.foo
'bar'
>>> s.baseProperty
'Thingee'
>>> 

Как указывает его комментарий, базовому классу AbstractClass не нужно использовать слоты, он может так же легко "неявно" определять атрибуты, устанавливая их в своем новом() инициализаторе, Например:

instance.otherBaseProperty = "Thingee2"

будет работать нормально. Также обратите внимание, что инициализатор базового класса поддерживает тривиальные инициализаторы (no-arg) в своих подтипах, а также инициализаторы с переменной поправкой и переменной длины. Я рекомендую всегда использовать эту форму, поскольку она не накладывает синтаксиса в случае простейшего (тривиального конструктора), но позволяет использовать более сложную функциональность, не требуя обслуживания.

Ответ 2

В примере, который вы предоставили, я сделаю это как в вашем подходе 1. Я бы увидел класс ABC в основном как помощник реализации для X и других классов, которые реализуют определенный интерфейс. Указанный интерфейс состоит из атрибута 'registry'.

По логике, по крайней мере, следует различать интерфейс, разделяемый X и другими классами, и базовый класс, который поможет вам его реализовать. То есть определите отдельно, что есть интерфейс (например, "ABC" ), который предоставляет список "реестр". Затем вы можете принять решение о том, чтобы исключить реализацию интерфейса как общий базовый класс (концептуально смешанный) для разработчиков интерфейса ABC, поскольку он очень легко вводит новые классы реализации (в дополнение к X).

Изменить: Что касается защиты от ошибок при реализации классов, я бы нацелил их на модульные тесты. Я думаю, что это более полно, чем пытаться объяснить все в вашей реализации:)

Ответ 3

Первый - лучший дизайн, потому что подклассам не нужно знать, что вы внедрили реестр со списком. Например, вы можете предложить функцию _is_in_registry, которая принимает один аргумент и возвращает элемент, находящийся в реестре. Затем вы можете изменить суперкласс и заменить список на набор, потому что элементы canonly появляются один раз в реестре, и вам не нужно будет менять подклассы.

Кроме того, это меньше кода: представьте, что у вас есть 100 полей, подобных этому в ABC, а ABC - 100 подклассов, таких как X...