Фабрики классов и абстрактные базовые классы

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

В качестве примера рассмотрим базовый класс Shape, который является просто контейнером для списка координат:

class Shape(object):
    """Cell shape base class."""
    def __init__(self, sequence):
        self.points = sequence

    @property
    def points(self):
        return self._points

    @points.setter
    def points(self, sequence):
        # Error checking goes here, e.g. check that `sequence` is a
        # sequence of numeric values.
        self._points = sequence

В идеале я хочу быть в состоянии определить, скажем, Square класс, где points.setter метод проверяет, что sequence имеет длину четыре. Кроме того, я бы хотел, чтобы пользователь не смог создать экземпляр Shape, Есть ли способ, которым я могу определить Shape быть абстрактным базовым классом? Я попытался изменить определение формы следующим образом:

import abc

class Shape(object):
    """Cell shape base class."""

    __metaclass__ = abc.ABCMeta

    def __init__(self, sequence):
        self.points = sequence

    @abc.abstractproperty
    def npoints(self):
        pass

    @property
    def points(self):
        return self._points

    @points.setter
    def points(self, sequence):
        # Error checking goes here...
        if len(sequence) != self.npoints:
            raise TypeError('Some descriptive error message!')

        self._points = sequence

Это требует подклассов для определения свойства npoints, Я могу тогда определить класс Square как

class Square(Shape):
    @property
    def npoints(self):
        return 4

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

def Factory(name, npoints):
    return type(name, (Shape,), dict(npoints=npoints))

Triangle = Factory('Triangle', 3)    
Square = Factory('Square', 4)
# etc...

Является ли эта фабричная функция класса подходящим подходом, или я забил npoints имущество? Лучше ли заменить звонок на type с чем-то более многословным, как:

def Factory(name, _npoints):
    class cls(Shape):
        @property
        def npoints(self):
            return _npoints
    cls.__name__ = name
    return cls

Альтернативный подход заключается в определении атрибута класса _NPOINTS и изменить npointsсобственностью Shape в

@property
def npoints(self):
    return _NPOINTS

Однако тогда я теряю преимущество использования абстрактного базового класса, поскольку:

  • Я не вижу, как определить атрибут класса с помощью type, а также
  • Я не знаю, как определить атрибут абстрактного класса.

У кого-нибудь есть мысли о том, как лучше реализовать этот абстрактный базовый класс и функцию фабрики классов, или даже вообще лучший дизайн?

1 ответ

Решение

Не зная больше о вашем проекте, я не могу дать конкретного совета по общему дизайну. Я просто приведу несколько общих советов и мыслей.

  1. Динамически генерируемые классы часто являются признаком того, что вам не нужны отдельные классы - просто напишите один класс, который объединяет все функциональные возможности. В чем проблема с Shape класс, который получает свои свойства во время создания экземпляра? (Конечно, есть причины использовать динамически генерируемые классы - namedtuple() Заводская функция является одним из примеров. Однако я не смог найти никаких конкретных причин в вашем вопросе.)

  2. Вместо использования абстрактных базовых классов вы часто просто документируете предполагаемый интерфейс, а затем пишете классы, соответствующие этому интерфейсу. Из-за динамической природы Python вам не нужен общий базовый класс. Часто есть другие преимущества для общего базового класса - например, общая функциональность.

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

Другие вопросы по тегам