Python3元类的多重继承

介绍

我们可以通过为类定义元类的方式控制对类本身的创建,有关元类的基本介绍可以参考Python3元类。在Python3的标准库中有一些使用元类的情况,例如abc模块中的ABC类的元类是ABCMeta。我们可以通过继承ABC类或是指定元类为ABCMeta的方式来自定义类,使该类具有抽象类的特征。例如以下代码:

import abc


class AbstractCls(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def hello(self):
        pass


class ClassA(AbstractCls):
    pass


ClassA()

""" output
TypeError: Can't instantiate abstract class ClassA with abstract methods hello
"""

当我们定义AbstractCls类时指定了元类为abc.ABCMeta,此时若该类或者该类的子类没有实现由装饰器"@abc.abstractmethod"装饰的方法时,都会被当做抽象类,当实例化的时候会报TypeError的错误。

元类的冲突

假如我们定义了一个元类Meta1, 并定义了类ClassWithMeta1(指定它的元类为Meta1), 示例代码如下:

class Meta1(type):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta1.__name__)
        return type.__new__(Meta1, *args, **kwargs)


class ClassWithMeta1(metaclass=Meta1):
    pass


""" output
ClassWithMeta1 is created by Meta1
"""

我们根据输出可以看到,当定义元类为Meta1的类ClassWithMeta1时,Python会用元类控制类的生成,换句话说,“class ClassWithMeta1(metaclass=Meta1)”,"ClassWithMeta1 = Meta1(“ClassWithMeta1”, (), {})"这两句代码的作用基本一样。因此元类中的__new__方法会被调用,输出语句后,最终由"type.__new__"返回实例对象(类),并将名字"ClassWithMetal"绑定到该对象上。
若现在有个需求是创建个类,可以通过元类Meta1控制并还具有抽象类的功能。不难发现Python不支持为某个类同时指定多于一个的元类类似(class B(metaclass=[Meta1, ABC]))。于是我们会想通过多重继承的方式实现相同的功能,例如以下代码。

import abc


class AbstractCls(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def hello(self):
        pass


class ClassA(AbstractCls):
    pass


class Meta1(type):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta1.__name__)
        return type.__new__(Meta1, *args, **kwargs)


class ClassWithMeta1(metaclass=Meta1):
    pass


class ClassWithAbstractAndMeta1(abc.ABC, ClassWithMeta1):
    pass


""" output
TypeError: metaclass conflict: the metaclass of a derived class must
 be a (non-strict) subclass of the metaclasses of all its bases
"""

像上述方式定义的类ClassWithAbstractAndMeta1,想通过继承分别包括不同元类的类abc.ABC和ClassWithMeta1实现对类控制,但编译器会报上面output中的错误。这个错误通俗地讲,ClassWithAbstractAndMeta1的所有父类(abc.ABC, ClassWithMeta1)的各自的所有元类(abc.ABCMeta, Meta1),不能找到某个元类使该元类为这些元类的子类。在这个例子中元类abc.ABCMeta和Meta1都不是各自的子类,因此会报元类冲突的错误。下面将介绍当一个类继承于多个类并且,存在多个包含不同元类的父类时,编译器是如何找到为该类创建的元类的。

类继承包含不同元类的多个父类

先直接看一个例子:

class Meta1(type):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta1.__name__)
        return type.__new__(Meta1, *args, **kwargs)


class Meta2(Meta1):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta2.__name__)
        return type.__new__(Meta2, *args, **kwargs)


class ClassWithMeta1(metaclass=Meta1):
    pass


class ClassWithMeta2(metaclass=Meta2):
    pass


class ClassWithMeta1AndMeta2(ClassWithMeta1, ClassWithMeta2):
    pass


print(type(ClassWithMeta1AndMeta2))

""" output
ClassWithMeta1 is created by Meta1
ClassWithMeta2 is created by Meta2
ClassWithMeta1AndMeta2 is created by Meta2
<class '__main__.Meta2'>
"""

在这里, 我们新建了两个元类(Meta1和Meta2),而且Meta2继承自Meta1,因此在定义ClassWithMeta1和ClassWithMeta2类时,会触发各自的元类的__new__方法和__init__方法。而当我们定义继承自这两个类的ClassWithMeta1AndMeta2类时,编译器可以找到一个元类(Meta2),该元类是ClassWithMeta1AndMeta2所有父类包含的不同元类的子类,因此不会发生元类冲突。ClassWithMeta1AndMeta2类成功定义后,我们通过type(ClassWithMeta1AndMeta2)得到ClassWithMeta1AndMeta2的元类为Meta2。
这里,在定义ClassWithMeta1AndMeta2时判断出它的元类为Meta2,因此触发Meta2(‘ClassWithMeta1AndMeta2’, (ClassWithMeta1, ClassWithMeta2), {}),进入Meta2的__new__方法,最终由return type.new(Meta2, *args, **kwargs),创建出ClassWithMeta1AndMeta2类。

通过多个元类控制

前面通过Meta2继承Meta1的方式,解决了元类冲突的问题,但如果有多个元类(互相不是各自的子类),而又需要用到这两个元类控制类的创建,那么则需要一个新的元类利用多重继承这些元类的方式实现。

class Meta1(type):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta1.__name__)
        return type.__new__(Meta1, *args, **kwargs)


class Meta2(type):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta2.__name__)
        return type.__new__(Meta2, *args, **kwargs)


class ClassWithMeta1(metaclass=Meta1):
    pass


class ClassWithMeta2(metaclass=Meta2):
    pass


class CombinedMeta(Meta1, Meta2):
    pass


class ClassWithMeta1AndMeta2(metaclass=CombinedMeta):
    pass


print(type(ClassWithMeta1AndMeta2))

""" output
ClassWithMeta1 is created by Meta1
ClassWithMeta2 is created by Meta2
ClassWithMeta1AndMeta2 is created by Meta1
<class '__main__.Meta1'>
"""

我们看到此时Meta2不再继承自Meta1,用了新的元类CombinedMeta多重继承了这两个元类,并将ClassWithMeta1AndMeta2的元类指定为CombinedMeta。但存在问题是,ClassWithMeta1AndMeta2的元类是Meta1,指执行了Meta1里的__new__方法。这个问题主要是由于多重继承中的mro顺序,因为ClassWithMeta1AndMeta2没有重写__new__方法,因此会默认调用super().__new__方法,这里就会发现Meta1中有__new__方法,在调用return type.new(Meta1, *args, **kwargs)时结束,因此type(ClassWithMeta1AndMeta2)会显示ClassWithMeta1AndMeta2的元类为Meta1。
若想解决上述问题,我们需要改造实现__new__方法的元类中的return语句。

class Meta1(type):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta1.__name__)
        return super().__new__(cls, *args, **kwargs)


class Meta2(type):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta2.__name__)
        return super().__new__(cls, *args, **kwargs)


class ClassWithMeta1(metaclass=Meta1):
    pass


class ClassWithMeta2(metaclass=Meta2):
    pass


class CombinedMeta(Meta1, Meta2):
    pass


class ClassWithMeta1AndMeta2(metaclass=CombinedMeta):
    pass


print(type(ClassWithMeta1AndMeta2))

""" output
ClassWithMeta1 is created by Meta1
ClassWithMeta2 is created by Meta2
ClassWithMeta1AndMeta2 is created by Meta1
ClassWithMeta1AndMeta2 is created by Meta2
<class '__main__.CombinedMeta'>
"""

在定义ClassWithMeta1AndMeta2时,通过前面的元类检查顺序得到该类的元类为CombinedMeta,因此会调用CombinedMeta(‘ClassWithMeta1AndMeta2’, (), {})生成ClassWithMeta1AndMeta2类,而我们没有重写CombinedMeta的__new__方法,因此在__new__方法中会直接返回super().__new__(CombinedMeta, *args, **kwargs),根据多重继承的mro顺序,会调用Meta1的__new__方法,打印出’ClassWithMeta1AndMeta2 is created by Meta1’语句,接着根据super的原则,会调用Meta2中的__new__方法,打印’ClassWithMeta1AndMeta2 is created by Meta2’语句,最后Meta2中的super.__new__等同于type.__new__(CombinedMeta, *args, **kwargs)。于是类ClassWithMeta1AndMeta2通过指定继承了Meta1和Met2的元类CombinedMeta被这个两个元类控制,实现了我们的目的。
因此我们注意在实现每个独立的元类的__new__方法时,需要注意最后应该使用super().__new__作为return。

最终实现

最后,为了使用方便,写了几个简单的函数来实现继承的封装,屏蔽了元类的冲突。

import itertools

META_CLS = 0
HAS_META_CLS = 1
COMMON_CLS = 2


def is_metaclass(cls):
    return issubclass(cls, type)


def get_metaclass(cls):
    return cls if is_metaclass(cls) else type(cls)


def has_defined_metaclass(cls):
    return get_metaclass(cls) is not type


def _get_class_flag(cls):
    if is_metaclass(cls):
        return META_CLS
    if has_defined_metaclass(cls):
        return HAS_META_CLS
    else:
        return COMMON_CLS


def _get_three_kinds_of_classes(*mixins):
    all_mixins_group = itertools.groupby(mixins, _get_class_flag)

    mixin_metaclass_list = []
    mixin_has_metaclass_list = []
    mixin_common_list = []

    for mixin_group in all_mixins_group:
        if mixin_group[0] == META_CLS:
            mixin_metaclass_list = list(mixin_group[1])
        elif mixin_group[0] == HAS_META_CLS:
            mixin_has_metaclass_list = list(mixin_group[1])
        elif mixin_group[0] == COMMON_CLS:
            mixin_common_list = list(mixin_group[1])

    return mixin_metaclass_list, mixin_has_metaclass_list, mixin_common_list


def _get_meta_cls_and_original_class(cls):
    bases = cls.__bases__
    new_bases = []
    meta_cls = type(cls)

    for base in bases:
        if has_defined_metaclass(base):
            _, new_base = _get_meta_cls_and_original_class(base)
            new_bases.append(new_base)

        else:
            new_bases.append(base)
    return meta_cls, type(cls.__name__, tuple(new_bases), dict(cls.__dict__))


def _combined_metaclass(*metas):
    class CombinedMeta(*metas):
        pass

    return CombinedMeta


def generate_base(*mixins):
    mixin_metaclass_list, mixin_has_metaclass_list, mixin_common_list = \
        _get_three_kinds_of_classes(*mixins)

    meta_and_common_list = [_get_meta_cls_and_original_class(mixin_has_metaclass)
                            for mixin_has_metaclass in mixin_has_metaclass_list]

    for metacls, commoncls in meta_and_common_list:
        mixin_metaclass_list.append(metacls)
        mixin_common_list.append(commoncls)

    meta_cls = _combined_metaclass(*mixin_metaclass_list) \
        if mixin_metaclass_list else type

    class BaseClass(metaclass=meta_cls, *mixin_common_list):
        @classmethod
        def get_metas(cls):
            mro = meta_cls.mro(meta_cls)

            def filter_name(name):
                filters = ['type', 'CombinedMeta', 'object']

                return not any((i in name for i in filters))

            metas = [m for m in mro if filter_name(m.__name__)]

            return metas

    return BaseClass

通过调用generate_base函数,我们可以传入三种类(元类,包含自定义元类的类,普通类),该函数会返回一个BaseClass类,该类由所有传入的元类控制,并继承了普通类和拥有自定义元类的类,下面通过例子来说明。在该函数中,我们会检查到包含自定义元类的类,并将它的元类剥离,和普通的元类组合并由一个元类继承它们实现对所有元类的控制,并且可以将普通元类和剥离元类后的类组合并有BaseClass继承,返回后的类便有了这些类的功能,而且是由所有元类控制。

class Meta1(type):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta1.__name__)
        return super().__new__(cls, *args, **kwargs)


class Meta2(type):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta2.__name__)
        return super().__new__(cls, *args, **kwargs)


class ClassWithMeta1(metaclass=Meta1):
    pass


class Common:
    pass


class Test(generate_base(abc.ABC, ClassWithMeta1, Meta2, Common)):
    pass

""" output
ClassWithMeta1 is created by Meta1
BaseClass is created by Meta2
BaseClass is created by Meta1
Test is created by Meta2
Test is created by Meta1
[<class '__main__.Meta2'>, <class 'abc.ABCMeta'>, <class '__main__.Meta1'>]
"""

Test类通过generate_base函数传入希望继承的类,我们这里传入了元类(Meta2), 有自定义元类的类(abc.ABC, ClassWithMeta1),和普通类Common。Test类现在会被Meta1和Meta2控制,并具有抽象类的功能。这里输出的BaseClass它的元类类似上文中的CombinedMeta继承了这三个元类(abc.ABC, Meta1, Meta2)。我们可以通过函数get_metas看到Test类由这三个元类控制。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值