У меня есть набор миксинов, которые можно динамически добавлять в базовый класс, смешивать и сопоставлять.Все они имеют одинаковые свойства и методы, но не должны перекрывать друг друга, когда объединяются несколько миксов.Лучше ли построить обычный класс MixinManager для обработки вещей или MetaClass, чтобы изменить поведение моего основного BaseClass в зависимости от того, какие миксины объединены вместе?
Например, все мои миксины переопределяют метод _full
.Тем не менее, я хочу, чтобы один full
метод вызывал какой-либо метод _full
, смешанный с моим базовым классом.
классы mixin
Эти миксины имеют общие методы и свойства.Я хотел бы избежать переопределения всех базовых методов в каждом подклассе mixin.Это много повторяющегося кода.
import six
import abc
import inspect
import sys
class BaseMixin(six.with_metaclass(abc.ABCMeta, object)):
@abc.abstractmethod
def _full(self):
pass
# not working yet
def has_name(self):
return self._name is not None
class AMixin(BaseMixin):
def __init__(self, db_name=None, **kwargs):
super(AMixin, self).__init__(**kwargs)
self.db_name = db_name
def _full(self):
print('i am an A mixin', self.db_name)
class BMixin(BaseMixin):
def __init__(self, ext_name=None, **kwargs):
super(BMixin, self).__init__(**kwargs)
self.ext_name = ext_name
def _full(self):
print('i am a B mixin', self.ext_name)
MIXINS = {name.lower().split('mixin')[0]: obj for name, obj in inspect.getmembers(sys.modules[__name__], inspect.isclass)
if 'Mixin' in name and 'Base' not in name}
класс mixin manager
Я могу заставить его работать с обычной системой менеджера классов.Однако мне нужно будет установить множество методов и свойств «менеджера», и они, похоже, станут громоздкими.Я не хочу жестко определять каждый метод / свойство, для которого мне нужно это сделать.
class MixinManager(object):
def __new__(cls, *args, **kwargs):
mixins = [obj for obj in cls.__bases__ if 'Mixin' in obj.__name__]
if len(mixins) == 1:
setattr(cls, 'full', mixins[0]._full)
return super(MixinManager, cls).__new__(cls, args, kwargs)
def full(self, mixin=None):
if mixin:
assert mixin.lower() in MIXINS.keys(), 'mixin must be one of {0}'.format(MIXINS.keys())
mix_obj = MIXINS[mixin]
mix_obj._full(self)
метакласс
Это работает, когда у меня есть только один миксин (например, TestA, TestB), но не работает, когда у меня есть несколько миксинов (например, TestAB).Я не могу заставить это работать с full
методом.Есть ли способ, чтобы метакласс определил новый метод full
в моем классе, который контролирует, как он вызывается в каждом экземпляре?Я не могу понять, как правильно это сделать.Я использую __call__
здесь, потому что я хотел контролировать, как создается класс при первом вызове каждого экземпляра.
class MixMeta(abc.ABCMeta):
def __new__(cls, *args, **kwargs):
return super(MixMeta, cls).__new__(cls, *args, **kwargs)
def __call__(cls, *args, **kwargs):
print('---')
print('call mixmeta', cls, args, kwargs)
mixins = [obj for obj in cls.__bases__ if 'Mixin' in obj.__name__]
newclass = super(MixMeta, cls).__call__(*args, **kwargs)
if len(mixins) == 1:
setattr(newclass, 'full', mixins[0](*args, **kwargs)._full)
else:
setattr(newclass, 'full', cls.full)
return newclass
def full(self, mixin=None):
print('in full', self)
if mixin:
assert mixin.lower() in MIXINS.keys(), 'mixin must be one of {0}'.format(MIXINS.keys())
mix_obj = MIXINS[mixin]
print('mixobj', mix_obj)
mix_obj(*self._args, **self._kwargs)._full()
базовые объекты
# mixmeta class
class BaseObject(six.with_metaclass(MixMeta, object)):
# mixin manager
class BaseObject(six.with_metaclass(abc.ABCMeta, MixinManager, object)):
def __new__(cls, *args, **kwargs):
print('in base object new first', cls.__bases__)
return super(BaseObject, cls).__new__(cls, args, kwargs)
def __init__(self, *args, **kwargs):
super(BaseObject, self).__init__(*args, **kwargs)
class TestBase(BaseObject):
pass
class TestA(BaseObject, AMixin):
pass
class TestB(BaseObject, BMixin):
pass
class TestAB(BaseObject, AMixin, BMixin):
pass
Создание экземпляров каждого класса
base = TestBase()
a = TestA(db_name='dba') (would like name='dba'; maps to db_name)
b = TestB(ext_name='extb') (would like name='extb' ...)
ab = TestAB(db_name='dba') (name ambiguous; this maps to db_name)
ab1 = TestAB(db_name='dba', ext_name='extb') (same as above, both names map)
ab2 = TestAB(db_name='dba', ext_name='extb')
С MixinManager это работает правильно.С MixMeta он не может правильно звонить.
a.full() # should print from AMixin
b.full() # should print from BMixin
ab.full() # should not print anything or throw ambiguity error
ab1.full(mixin='a') # should print from AMixin
ab2.full(mixin='b') # should print from BMixin
Точно так же я хочу сделать то же самое со свойствами.Я хотел бы сопоставить общее свойство name
с _name
, и оно динамически сопоставляется с тем, что вызывается mixin.Однако имена не должны переопределяться, когда несколько миксинов объединяются вместе.
a.has_name() # should find and print db_name from AMixin (only db_name set)
b.has_name() # should find and print ext_name from BMixin (only ext_name set)
ab.has_name() # should print nothing or throw ambiguity error
ab1.has_name(mixin='a') # should print db_name (both db and ext names set)
ab2.has_name(mixin='b') # should print ext_name (both db and ext names set)