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__ 函数不会自动调用吗?的主要内容,如果未能解决你的问题,请参考以下文章