如何使用元类实现多重继承?

8

我正在尝试使用注册模式(registry pattern)来注册所有我用 Flask-RESTFUL 定义的资源。

from flask_restful import Resource

class ResourceRegistry(type):

    REGISTRY = {}

    def __new__(cls, name, bases, attrs):
        new_cls = type.__new__(cls, name, bases, attrs)
        cls.REGISTRY[new_cls.__name__] = new_cls
        return new_cls

    @classmethod
    def get_registry(cls):
        return dict(cls.REGISTRY)


class BaseRegistered(object):
    __metaclass__ = ResourceRegistry


class DefaultResource(BaseRegistered, Resource):

    @classmethod
    def get_resource_name(cls):
        s = re.sub('(.)([A-Z][a-z]+)', r'\1-\2', cls.__name__)
        return '/' + re.sub('([a-z0-9])([A-Z])', r'\1-\2', s).lower()

当整个事情启动时,我会得到以下结果:
TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

我尝试了使用代理类的层级,但结果仍然相同。那么有没有一种方法可以使用这种模式注册我的资源?

3个回答

17
一个元类是用来创建一个类的类,这意味着一个类的结构 - 例如它的内部 __dict__ 或者由 __getattribute__ 提供的自动委派机制 - 是由元类创建到该类中的。
当你从父类继承时,你隐式地继承了它的元类,因为你基本上只是在扩展父类的结构,这在一定程度上是由其元类提供的。
所以,当你从类继承时,你要么保留被父类给予的元类,要么定义一个派生自你的父类的元类。在多重继承的情况下也是如此。
以下是 Python 错误代码示例:
class ParentType(type):
    pass

class Parent(metaclass=ParentType):
    pass

class ChildType(type):
    pass

class Child(Parent, metaclass=ChildType):
    pass

这正好会导致你在问题中提到的错误。将ChildType作为ParentType的子类可以解决这个问题。

class ParentType(type):
    pass

class Parent(metaclass=ParentType):
    pass

class ChildType(ParentType):
    pass

class Child(Parent, metaclass=ChildType):
    pass

Dologan的答案正确地解决了创造一个同时继承元类并直接使用它的问题。

但是,如果可能,我会避免使用这样复杂的解决方案。多重继承有点难以管理 - 这并不是坏事,只是很复杂 - 在元类中使用它可能会导致一些难以理解的东西。

这显然取决于您想要实现什么以及您如何记录和测试解决方案的好坏。如果您想或需要使用元类来解决它,我建议利用抽象基类(基本上是类别)。ABCs包含其_abc_registry属性中注册的类列表,但没有官方方法可以获取它,最好避免直接使用它。您可以从ABCMeta派生,并使用此代码使其保持公共注册表。

import abc

class RegistryABCMeta(abc.ABCMeta):
    def __init__(self, name, bases, namespace):
        super().__init__(name, bases, namespace)
        self.registry = []

    def register(self, subclass):
        super().register(subclass)
        self.registry.append(subclass)


class RegistryABC(metaclass=RegistryABCMeta):
    pass

现在您可以创建继承自RegistryABC并使用register()方法的类别:

class MyCategory(RegistryABC):
    pass

MyCategory.register(tuple)
MyCategory.register(str)

print(MyCategory.registry)

你得到的是这里的[<class 'tuple'>, <class 'str'>]


7

您的DefaultResource类似乎从两个具有不同元类的类继承:BaseRegistered(具有元类ResourceRegistry)和Resource(具有Flask的MethodViewType元类)。

这个答案建议执行以下操作:

from flask.views import MethodViewType    

class CombinedType(ResourceRegistry, MethodViewType):
    pass

class BaseRegistered(object):
    __metaclass__ = Combinedtype

然后像以前一样继续进行。

0

它不需要很复杂,但您必须决定是否还应在REGISTRY中注册一些ResourceMixins(这里将会)。在Python 2中,只需使用__metaclass__。我花了几个小时,因为我没有保存代码,一直在想这是什么魔法。

在我的情况下,我想添加额外的method_decorators,但Resource类已经声明了它,所以无法将其添加到attrs中,而是设置在new_cls对象上。还要记得调用MethodViewType.__new__(super(),而不是type)。

from flask_restful import Resource
from flask.views import MethodViewType

class ResourceRegistry(MethodViewType):

    REGISTRY = {}

    def __new__(cls, name, bases, attrs):
        new_cls = super().__new__(cls, name, bases, attrs)
        cls.REGISTRY[new_cls.__name__] = new_cls
        return new_cls

    @classmethod
    def get_registry(cls):
        return dict(cls.REGISTRY)


class DefaultResource(Resource, metaclass=ResourceRegistry):

    @classmethod
    def get_resource_name(cls):
        s = re.sub('(.)([A-Z][a-z]+)', r'\1-\2', cls.__name__)
        return '/' + re.sub('([a-z0-9])([A-Z])', r'\1-\2', s).lower()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接