Выбрать подкласс на основе параметра

У меня есть модуль (db.py), который загружает данные из разных типов баз данных (sqlite,mysql и т. Д.), Модуль содержит класс db_loader и подклассы (sqlite_loader,mysql_loader), которые наследуются от него.

Тип используемой базы данных находится в отдельном файле параметров,

Как пользователь получает правильный объект обратно?

т.е. как мне сделать:

loader = db.loader()

Я использую метод loader в модуле db.py или есть более элегантный способ, с помощью которого класс может выбирать свой собственный подкласс на основе параметра? Есть ли стандартный способ сделать это?

3 ответа

Решение

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

class Animal(object):

    @staticmethod
    def get_animal_which_makes_noise(noise):
        if noise == 'meow':
            return Cat()
        elif noise == 'woof':
            return Dog()

class Cat(Animal):
    ...

class Dog(Animal):
    ...

есть ли более элегантный способ, с помощью которого класс может выбрать свой собственный подкласс на основе параметра?

Вы можете сделать это, переопределив свой базовый класс __new__метод. Это позволит вам просто уйтиloader = db_loader(db_type) а также loaderбудет волшебно быть правильным подклассом для типа базы данных. Это решение немного сложнее, чем другие ответы, но IMHO, безусловно, наиболее элегантно.

В простейшей форме:

class Parent():
    def __new__(cls, feature):
        subclass_map = {subclass.feature: subclass for subclass in cls.__subclasses__()}
        subclass = subclass_map[feature]
        instance = super(Parent, subclass).__new__(subclass)
        return instance

class Child1(Parent):
    feature = 1

class Child2(Parent):
    feature = 2

type(Parent(1))  # <class '__main__.Child1'>
type(Parent(2))  # <class '__main__.Child2'>

(Обратите внимание, что пока __new__ возвращает экземпляр cls, экземпляр __init__ будет автоматически вызван для вас.)

Однако у этой простой версии есть проблемы, и ее нужно будет расширить и адаптировать к вашему желаемому поведению. В частности, вы, вероятно, захотите решить следующие вопросы:

Parent(3)  # KeyError
Child1(1)  # KeyError

Поэтому я бы рекомендовал добавить cls к subclass_map или использовать его по умолчанию, например subclass_map.get(feature, cls). Если ваш базовый класс не предназначен для создания экземпляров - может быть, у него даже есть абстрактные методы? - тогда рекомендую датьParent метакласс abc.ABCMeta.

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

Это решение красивее, чем шаблон заводского метода ИМХО. И в отличие от некоторых других ответов, он самоподдерживающийся, потому что список подклассов создается динамически, а не хранится в жестко заданном сопоставлении. И это будет только создавать экземпляры подклассов, в отличие от одного из других ответов, которые будут создавать экземпляры чего-либо в глобальном пространстве имен, соответствующих данному параметру.

Я бы сохранил имя подкласса в файле params и имел бы фабричный метод, который бы создавал экземпляр класса с учетом его имени:

class loader(object):
  @staticmethod
  def get_loader(name):
    return globals()[name]()

class sqlite_loader(loader): pass

class mysql_loader(loader): pass

print type(loader.get_loader('sqlite_loader'))
print type(loader.get_loader('mysql_loader'))

Храните классы в dict, создайте правильный экземпляр на основе вашего параметра:

db_loaders = dict(sqlite=sqlite_loader, mysql=mysql_loader)
loader = db_loaders.get(db_type, default_loader)()

где db_type это параметр, который вы включаете, и sqlite_loader а также mysql_loader классы "загрузчик"

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