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

74 投票
4 回答
46072 浏览
提问于 2025-04-16 18:10

我想用一个混入类(Mixin)来给我的子类添加一些初始化功能,这些子类都继承自不同的API基础类。具体来说,我想创建多个不同的子类,它们都继承自这些不同的API提供的基础类和一个混入类,这样混入类的初始化代码就能以相同的方式执行,而不需要重复写代码。然而,似乎混入类的__init__函数不会被自动调用,除非我在子类的__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

从下面的输出可以看到,混入类的初始化函数从来没有被调用:

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

有没有办法让混入类的初始化函数被调用,而不需要在每个子类里都明确调用混入的初始化函数呢?(也就是说,不用在每个类里都写类似这样的代码:)

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

顺便说一下,如果我的所有子类都继承自同一个基础类,我知道我可以创建一个新的中间类,它同时继承基础类和混入类,这样可以避免代码重复。但是现在它们是从不同的基础类继承的,但又有一些共同的功能。(具体来说,是Django的字段类)。

4 个回答

28

在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的情况,可以在类定义后使用MixedClass = mixedomatic(MixedClass)

在Python 3中,指定元类的语法有所不同,因此你需要使用:

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

而不是上面显示的内容,你需要使用:

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

类装饰器的版本在两个版本中都可以直接使用。

34

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

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

抱歉我这么晚才看到这个,不过

class MixedClass2(SomeMixin, MyClass):
    pass

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

这里提到的模式叫做“协作多重继承”,这个方法很不错。但是如果一个基础类不想合作,那就把它放在第二个基础类的位置,把你的混合类放在第一个。这样,混合类的 __init__()(还有它定义的其他东西)会在基础类之前被检查,这符合Python的 方法解析顺序(MRO)

这样应该能解决一般的问题,虽然我不确定这是否适合你具体的情况。基础类如果有自定义的元类(比如Django模型)或者有奇怪的装饰器(像@martineau的回答那样;)可能会出现一些奇怪的情况。

撰写回答