Не активируются ли функции Mixin класса __init__?
Я хотел бы использовать Mixin, чтобы всегда добавлять некоторые функции init к моим дочерним классам, каждый из которых наследуется от разных базовых классов API. В частности, я хотел бы создать несколько разных дочерних классов, которые наследуют один из этих базовых классов, поставляемых API, и один Mixin, который всегда будет иметь код инициализации Mixin, выполненный таким же образом, без репликации кода. Тем не менее, кажется, что функция __init__ класса Mixin никогда не вызывается, если я явно не назову ее в функции класса __init__ класса Child, которая является менее идеальной. Я создал простой тестовый пример:
class APIBaseClassOne(object):
def __init__(self, *args, **kwargs):
print (" base ")
class SomeMixin(object):
def __init__(self, *args, **kwargs):
print (" mixin before ")
super(SomeMixin, self).__init__(*args, **kwargs)
print (" mixin after ")
class MyClass(APIBaseClassOne):
pass
class MixedClass(MyClass, SomeMixin):
pass
Как вы можете видеть в следующем выпуске, инициализация функции Mixin никогда не вызывается:
>>> import test
>>> test.MixedClass()
base
<test.MixedClass object at 0x1004cc850>
Есть ли способ сделать это (иметь функцию init в вызове Mixin), не записывая каждый дочерний класс для явного вызова функции инициализации Mixin? (т.е. без необходимости делать что-то подобное в каждом классе:)
class MixedClass(MyClass, SomeMixin):
def __init__(*args, **kwargs):
SomeMixin.__init__(self, *args, **kwargs)
MyClass.__init__(self, *args, **kwargs)
Btw, если все мои дочерние классы наследовались от одного базового класса, я понимаю, что могу создать новый средний класс, который наследуется от базового класса и mixin и держит его DRY таким образом. Однако они наследуются от разных базовых классов с общей функциональностью. (Классы Django Field, если быть точным).
Ответы
Ответ 1
Извините, я видел это так поздно, но
class MixedClass2(SomeMixin, MyClass):
pass
>>> m = MixedClass2()
mixin before
base
mixin after
Образец @Ignacio говорит о том, что он называется совместным множественным наследованием, и это здорово. Но если базовый класс не заинтересован в сотрудничестве, сделайте его второй базой, а ваш mixin первым. Mixin __init__()
(и все, что он определяет) будет проверяться перед базовым классом, следуя Python MRO.
Это должно решить общий вопрос, хотя я не уверен, что он справится с вашим конкретным использованием. Базовые классы с пользовательскими метаклассами (например, модели Django) или со странными декораторами (например, @martineau answer;) могут делать сумасшедшие вещи.
Ответ 2
Попросите базовый класс вызвать super().__init__()
, хотя он является подклассом object
. Таким образом будут запущены все методы __init__()
.
class BaseClassOne(object):
def __init__(self, *args, **kwargs):
super(BaseClassOne, self).__init__(*args, **kwargs)
print (" base ")
Ответ 3
Python не выполняет никаких неявных вызовов методам __init__
класса super-class (es), но это позволяет сделать это автоматически. Один из способов заключается в определении метакласса для вашего смешанного класса (классов), который создает или расширяет метод смешанного класса __init__
, чтобы он вызывал все перечисленные базы "__init__
" в том порядке, в котором они были перечислены.
Второй способ - использовать декоратор класса, который показан ниже в разделе под названием Изменить.
Использование метакласса:
class APIBaseClassOne(object): # API class (can't be changed)
def __init__(self, *args, **kwargs):
print ' APIBaseClassOne.__init__()'
class SomeMixin(object):
def __init__(self, *args, **kwargs):
print ' SomeMixin.__init__()'
class MixedClassMeta(type):
def __new__(cls, name, bases, classdict):
classinit = classdict.get('__init__') # Possibly None.
# define an __init__ function for the new class
def __init__(self, *args, **kwargs):
# call the __init__ functions of all the bases
for base in type(self).__bases__:
base.__init__(self, *args, **kwargs)
# also call any __init__ function that was in the new class
if classinit:
classinit(self, *args, **kwargs)
# add the local function to the new class
classdict['__init__'] = __init__
return type.__new__(cls, name, bases, classdict)
class MixedClass(APIBaseClassOne, SomeMixin):
__metaclass__ = MixedClassMeta # important
# if exists, is called after the __init__ of all the direct bases
def __init__(self, *args, **kwargs):
print ' MixedClass.__init__()'
print('MixedClass():')
MixedClass()
Вывод:
MixedClass():
APIBaseClassOne.__init__()
SomeMixin.__init__()
MixedClass.__init__()
Edit
Здесь, как выполнить одно и то же с декоратором класса (требуется Python 2.6 +):
class APIBaseClassOne(object): # API class (can't be changed)
def __init__(self, *args, **kwargs):
print)' APIBaseClassOne.__init__()')
class SomeMixin(object):
def __init__(self, *args, **kwargs):
print(' SomeMixin.__init__()')
def mixedomatic(cls):
""" Mixed-in class decorator. """
classinit = cls.__dict__.get('__init__') # Possibly None.
# define an __init__ function for the class
def __init__(self, *args, **kwargs):
# call the __init__ functions of all the bases
for base in cls.__bases__:
base.__init__(self, *args, **kwargs)
# also call any __init__ function that was in the class
if classinit:
classinit(self, *args, **kwargs)
# make the local function the class __init__
setattr(cls, '__init__', __init__)
return cls
@mixedomatic
class MixedClass(APIBaseClassOne, SomeMixin):
# if exists, is called after the __init__ of all the direct base classes
def __init__(self, *args, **kwargs):
print(' MixedClass.__init__()')
print('MixedClass():')
MixedClass()
(для Python < 2.6 используйте MixedClass = mixedomatic(MixedClass)
после определения класса.)