Выбрать подкласс на основе параметра
У меня есть модуль (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
классы "загрузчик"