web-dev-qa-db-ja.com

Mixinクラスの__init__関数は自動的に呼び出されませんか?

Mixinを使用して、それぞれが異なるAPIベースクラスから継承する子クラスにいくつかの初期化機能を常に追加したいと思います。具体的には、これらの異なるAPIが提供する基本クラスの1つと、コードレプリケーションなしで常に同じ方法で実行されるMixin初期化コードを持つ1つのMixinから継承する複数の異なる子クラスを作成したいと思います。ただし、子クラスの__init__関数で明示的に呼び出さない限り、Mixinクラスの__init__関数は呼び出されないようです。これは理想的ではありません。簡単なテストケースを作成しました。

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関数のinitは呼び出されません。

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

すべての子クラスを作成せずにこれを実行して(Mixinのinit関数を呼び出す)、Mixinのinit関数を明示的に呼び出す方法はありますか? (つまり、すべてのクラスでこのようなことをする必要はありません:)

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

ところで、すべての子クラスが同じ基本クラスから継承している場合、基本クラスとミックスインから継承する新しい中間クラスを作成し、それをDRYそのままにしておくことができます。それらは、共通の機能を持つ異なる基本クラスから継承します(正確にはDjango Fieldクラス)。

49
Ben Roberts

すみません、こんなに遅く見ましたが、

_class MixedClass2(SomeMixin, MyClass):
    pass

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

@Ignacioが話しているパターンは、協調的多重継承と呼ばれ、素晴らしいです。ただし、基本クラスが協力することに興味がない場合は、それを2番目のベースにして、最初のミックスインを作成します。 Pythonの [〜#〜] mro [〜#〜] に従って、ミックスインの__init__()(およびそれが定義する他のすべて)が基本クラスの前にチェックされます。

これは一般的な問題を解決するはずですが、特定の用途を処理するかどうかはわかりません。カスタムメタクラス(Djangoモデルなど)または奇妙なデコレーター(@martineauの答え;など)を含む基本クラスは、クレイジーなことをすることができます。

42
Matt Luongo

objectのサブクラスであっても、基本クラスにsuper().__init__()を呼び出させます。これにより、すべての__init__()メソッドが実行されます。

class BaseClassOne(object):
    def __init__(self, *args, **kwargs):
        super(BaseClassOne, self).__init__(*args, **kwargs)
        print (" base ")

Pythonは、クラスのスーパークラスの___init___メソッドへの暗黙的な呼び出しを実行しませんが、自動的に呼び出すことは可能です。 1つの方法は、混合クラスの___init___メソッドを作成または拡張する混合クラスのメタクラスを定義して、リストされたすべてのベースの___init___関数をリストされた順に呼び出すようにすることです。

2番目の方法は、以下のEditセクションに示されているクラスデコレータを使用することです。

メタクラスの使用:

_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、クラス定義のMixedClass = mixedomatic(MixedClass)followingを使用します。

Python 3では、メタクラスを指定する構文が異なるため、次の代わりに:

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

上記に示すように、使用する必要があります:

_class MixedClass(APIBaseClassOne, SomeMixin, metaclass=MixedClassMeta):
_

クラスデコレータのバージョンは、両方のバージョンでそのまま動作します。

18
martineau