首页 文章

Mixin类__init__函数是不是自动调用的?

提问于
浏览
43

我想使用Mixin总是为我的子类添加一些init功能,每个类继承自不同的API基类 . 具体来说,我想创建多个不同的子类,这些子类继承自这些不同的API提供的基类之一,以及一个Mixin,它将始终以相同的方式执行Mixin初始化代码,而无需代码复制 . 但是,看起来Mixin类的__init__函数永远不会被调用,除非我在Child类的__init__函数中明确地调用它,这不太理想 . 我 Build 了一个简单的测试用例:

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 做这样的事:)

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

顺便说一下,如果我所有的子类都继承自相同的基类,我意识到我可以创建一个继承自基类和mixin的新中间类,并保持干燥 . 但是,它们从具有共同功能的不同基类继承 . (准确地说是Django Field类) .

3 回答

  • 21

    Python不会对类' super-class(es)—but it'的 __init__ 方法执行任何隐式调用,以使其自动发生 . 一种方法是为混合类定义一个元类,它创建或扩展混合类' __init__ 方法,以便按列出的顺序调用所有列出的基数' __init__ 函数 .

    第二种方法是使用类装饰器 - 如下面的 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__()
    

    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'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()
    

    Notes

    对于Python <2.6,请在类定义后使用 MixedClass = mixedomatic(MixedClass) .

    在Python 3中,指定元类的语法是不同的,因此不是:

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

    如上所示,您需要使用:

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

    类装饰器版本将在两个版本中按原样运行 .

  • 32

    让基类调用 super().__init__() ,即使它是 object 的子类 . 这样,所有 __init__() 方法都将运行 .

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

    对不起,我这么晚才看到这个,但是

    class MixedClass2(SomeMixin, MyClass):
        pass
    
    >>> m = MixedClass2()
     mixin before 
     base 
     mixin after
    

    @Ignacio所说的模式被称为合作多重继承,它对合作感兴趣,使它成为第二个基础,并且你的第一个混合 . 在Python的MRO之后,将在基类之前检查mixin的 __init__() (及其定义的任何其他内容) .

    这应解决一般性问题,但我不确定它是否能够处理您的具体用途 . 具有自定义元类(如Django模型)或奇怪装饰器(如@ martineau的答案;)的基类可以做疯狂的事情 .

相关问题