Функции класса __init__ класса Mixin не вызываются автоматически? - PullRequest
49 голосов
/ 23 мая 2011

Я бы хотел использовать Mixin, чтобы всегда добавлять некоторые функции инициализации в мои дочерние классы, каждый из которых наследуется от разных базовых классов 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

Как вы можете видеть в следующем выводе, init функции Mixin никогда не вызывается:

>>> import test
>>> test.MixedClass()
 base
<test.MixedClass object at 0x1004cc850>

Есть ли способсделать это (вызвать функцию init в Mixin) без написания каждого дочернего класса для явного вызова функции init Mixin?(т.е. без необходимости делать что-то подобное в каждом классе:)

class MixedClass(MyClass, SomeMixin):
    def __init__(*args, **kwargs):
        SomeMixin.__init__(self, *args, **kwargs)
        MyClass.__init__(self, *args, **kwargs) 

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

Ответы [ 3 ]

39 голосов
/ 02 февраля 2012

Извините, я видел это так поздно, но

class MixedClass2(SomeMixin, MyClass):
    pass

>>> m = MixedClass2()
 mixin before 
 base 
 mixin after

Паттерн, о котором говорит @Ignacio, называется кооперативным множественным наследованием, и это здорово.Но если базовый класс не заинтересован в сотрудничестве, сделайте его вторым, а ваш микс - первым.__init__() миксина (и все остальное, что он определяет) будет проверен перед базовым классом, после MRO .

. * Python Python. Это должно решить общий вопрос, хотя я не уверен в этомобрабатывает ваше конкретное использование.Базовые классы с пользовательскими метаклассами (например, модели Django) или со странными декораторами (как ответ @ martineau;) могут делать сумасшедшие вещи.
23 голосов
/ 23 мая 2011

Базовый класс должен вызывать super().__init__(), даже если это подкласс object.Таким образом будут выполняться все методы __init__().

class BaseClassOne(object):
    def __init__(self, *args, **kwargs):
        super(BaseClassOne, self).__init__(*args, **kwargs)
        print (" base ")
18 голосов
/ 23 мая 2011

Python не выполняет неявных вызовов к __init__ методам суперкласса (ов) класса, но это возможно сделать автоматически.Одним из способов является определение метакласса для вашего смешанного класса (классов), который создает или расширяет метод __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, called after the __init__'s of all the direct bases.
    def __init__(self, *args, **kwargs):
        print('  MixedClass.__init__()')

print('MixedClass():')
MixedClass()

Вывод:

MixedClass():
  APIBaseClassOne.__init__()
  SomeMixin.__init__()
  MixedClass.__init__()

Редактировать

Вот как выполнить то же самое с декоратором классов (требуется 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's __init__.
    setattr(cls, '__init__', __init__)
    return cls

@mixedomatic
class MixedClass(APIBaseClassOne, SomeMixin):
    # If exists, called after the __init__'s of all the direct base classes.
    def __init__(self, *args, **kwargs):
        print('  MixedClass.__init__()')

print('MixedClass():')
MixedClass()

Примечания

Для Python <2.6 используйте <code>MixedClass = mixedomatic(MixedClass) после определения класса.

В Python 3 синтаксис для определения метаклассов отличаетсяпоэтому вместо:

class MixedClass(APIBaseClassOne, SomeMixin):
    __metaclass__ = MixedClassMeta  # important

, показанного выше, вам нужно будет использовать:

class MixedClass(APIBaseClassOne, SomeMixin, metaclass=MixedClassMeta):

Версия декоратора класса будет работать как есть в обеих версиях.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...