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

Posted

技术标签:

【中文标题】Mixin 类 __init__ 函数不会自动调用吗?【英文标题】:Are Mixin class __init__ functions not automatically called? 【发布时间】:2011-08-31 05:39:15 【问题描述】:

我想使用 Mixin 始终向我的子类添加一些初始化功能,每个子类都继承自不同的 API 基类。具体来说,我想创建多个不同的子类,这些子类继承自这些不同的 API 提供的基类之一和一个 Mixin,它将始终以相同的方式执行 Mixin 初始化代码,无需代码复制。但是,除非我在 Child 类的 __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) 

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

【问题讨论】:

一般来说,对不是为它设计的基类使用多重继承是个坏主意。混入类通常是一起设计的,混入任意类会产生这样的混乱。在任何情况下,如果两个基类都有一个__init__ 方法,解释器应该如何知道要调用哪个,或者以什么顺序调用它们? @André Caron:它可以像 C++ 一样确定顺序,其中基类按声明顺序初始化。 【参考方案1】:

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 MixedClass = mixedomatic(MixedClass) 遵循类定义。

在 Python 3 中,指定元类的语法有所不同,所以用以下方式代替:

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

如上所示,您需要使用:

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

类装饰器版本将在两个版本中按原样工作。

【讨论】:

感谢您展示这一点。虽然另一个答案中的讨论让我重新思考它是否是一个好主意,但这个实现很有效,我学到了很多。 @Ben:是的,mixin 有点争议,甚至被一些人认为是有害的,而且在 Python 中使用它们有点复杂,因为它默认不调用基类构造函数(不像,比如说,C++ 会)。但是,在您无法修改 API 类的情况下,可能可以使用它们 - 但您可能需要更多地考虑替代方案来做您需要的事情,例如子类化或包装 API 的类反而。另一种可能性可能是将它的实例作为您自己的一个类的属性。 不确定,希望我能同时接受,但你的已经被重新接受了 :) @Ben:谢谢。我可以理解您的困境,但我的 [偏见] 意见是我的更好,因为它不仅实际上解决了您的特定问题,而且设法以通用的方式解决了问题——因此比仅仅调用基类的init()。现实世界的问题可能就是这样。 我真的很喜欢混合型装饰器。我稍微修改了一下:classinit = cls.__init__ if '__init__' in cls.__dict__ else None【参考方案2】:

很抱歉,我这么晚才看到,但是

class MixedClass2(SomeMixin, MyClass):
    pass

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

@Ignacio 所说的模式称为协作多重继承,非常棒。但是如果一个基类对合作不感兴趣,那就让它成为第二个基类,而你的 mixin 是第一个。将在基类之前检查 mixin 的 __init__()(以及它定义的任何其他内容),遵循 Python 的 MRO。

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

【讨论】:

即使为时已晚,这也很好地归结为(尽管其他答案提供了一些非常好的教学)——最终,在开始任何多重继承冒险之前,对 Python MRO 的理解似乎至关重要,或者会发生意想不到的事情。谢谢! 欢迎您!完全正确,不幸的是,MRO 被作为一个高级主题提出:/【参考方案3】:

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

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

【讨论】:

谢谢!我没有意识到调用 super 会导致所有 __init__() 方法运行。不幸的是 BaseClassOne 是一个 API (Django) 提供的基类(我已经更新了我的问题以反映这一点),但这可能会让我朝着正确的方向开始。 它不会导致 all 全部运行,但会导致 next 运行,即使它在一个混入。 每天学习新东西... ;-) 谢谢。 @kindall:我认为很多人都忽略了 OP 无法更改 BaseClassOne 因为它是他们无法控制的 API 的一部分。 @martineau:这个事实是在我发布这个答案之后才被揭露的。这并不会使这个答案不正确,只是与情况的相关性要低得多。

以上是关于Mixin 类 __init__ 函数不会自动调用吗?的主要内容,如果未能解决你的问题,请参考以下文章

组合,Mixin,类类对象实例对象

python小练习--函数调用函数,让对象具有能动性

魔术方法

Python中super()和__init__()方法

python 类函数

Python-函数