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

Ben*_*rts 52 python inheritance multiple-inheritance mixins

我想使用Mixin总是为我的子类添加一些init功能,每个类继承自不同的API基类.具体来说,我想创建多个不同的子类,这些子类继承自这些不同的API提供的基类之一和一个Mixin,它将始终以相同的方式执行Mixin初始化代码,而无需代码复制.但是,似乎不会调用Mixin类的__init__函数,除非我在Child类的__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
Run Code Online (Sandbox Code Playgroud)

正如您在以下输出中所看到的,Mixin函数的init永远不会被调用:

>>> import test
>>> test.MixedClass()
 base
<test.MixedClass object at 0x1004cc850>
Run Code Online (Sandbox Code Playgroud)

有没有办法做到这一点(在调用Mixin时有一个init函数)而不编写每个子类来显式调用Mixin的init函数?(即,不必在每个班级做这样的事:)

class MixedClass(MyClass, SomeMixin):
    def __init__(*args, **kwargs):
        SomeMixin.__init__(self, *args, **kwargs)
        MyClass.__init__(self, *args, **kwargs) 
Run Code Online (Sandbox Code Playgroud)

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

Mat*_*ngo 44

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

class MixedClass2(SomeMixin, MyClass):
    pass

>>> m = MixedClass2()
 mixin before 
 base 
 mixin after
Run Code Online (Sandbox Code Playgroud)

@Ignacio所讨论的模式称为协同多重继承,它很棒.但是如果基类对合作不感兴趣,那么将它作为第二个基础,并将其作为第一个基础.在__init__()Python的MRO之后,将在基类之前检查mixin (以及它定义的任何其他内容).

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

  • 即使已经晚了,这也很好地概括了它(尽管其他答案提供了一些非常好的教学)——最终,在开始任何多重继承冒险之前,对 Python MRO 的理解似乎至关重要,否则会发生意想不到的事情。谢谢! (2认同)

Ign*_*ams 25

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

class BaseClassOne(object):
    def __init__(self, *args, **kwargs):
        super(BaseClassOne, self).__init__(*args, **kwargs)
        print (" base ")
Run Code Online (Sandbox Code Playgroud)

  • 它不会导致*all*运行,但它会导致*next*one运行,即使它在mixin中也是如此. (7认同)
  • @martineau:这个事实是在我发布这个答案之后才公布的.这并没有使这个答案不正确,只是与情况相关性太低了. (2认同)

mar*_*eau 18

Python不会__init__对类的超类(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, called after the __init__'s of all the direct bases.
    def __init__(self, *args, **kwargs):
        print('  MixedClass.__init__()')

print('MixedClass():')
MixedClass()
Run Code Online (Sandbox Code Playgroud)

输出:

MixedClass():
  APIBaseClassOne.__init__()
  SomeMixin.__init__()
  MixedClass.__init__()
Run Code Online (Sandbox Code Playgroud)

编辑

以下是如何使用类装饰器完成相同的操作(需要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()
Run Code Online (Sandbox Code Playgroud)

笔记

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

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

class MixedClass(APIBaseClassOne, SomeMixin):
    __metaclass__ = MixedClassMeta  # important
Run Code Online (Sandbox Code Playgroud)

如上所示,您需要使用:

class MixedClass(APIBaseClassOne, SomeMixin, metaclass=MixedClassMeta):
Run Code Online (Sandbox Code Playgroud)

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