Mixin类的__init__函数不会自动调用吗?
我想用一个混入类(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 个回答
在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):
类装饰器的版本在两个版本中都可以直接使用。
让基类调用 super().__init__()
,即使它是 object
的子类。这样所有的 __init__()
方法都会被执行。
class BaseClassOne(object):
def __init__(self, *args, **kwargs):
super(BaseClassOne, self).__init__(*args, **kwargs)
print (" base ")
抱歉我这么晚才看到这个,不过
class MixedClass2(SomeMixin, MyClass):
pass
>>> m = MixedClass2()
mixin before
base
mixin after
这里提到的模式叫做“协作多重继承”,这个方法很不错。但是如果一个基础类不想合作,那就把它放在第二个基础类的位置,把你的混合类放在第一个。这样,混合类的 __init__()
(还有它定义的其他东西)会在基础类之前被检查,这符合Python的 方法解析顺序(MRO)。
这样应该能解决一般的问题,虽然我不确定这是否适合你具体的情况。基础类如果有自定义的元类(比如Django模型)或者有奇怪的装饰器(像@martineau的回答那样;)可能会出现一些奇怪的情况。