如何在元类中使用多重继承?

时间:2016-03-22 10:35:40

标签: python flask metaprogramming flask-restful

我尝试使用注册表模式注册我使用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 个答案:

答案 0 :(得分:8)

元类是用于构建类的类,这意味着创建了一个类的结构 - 例如它的内部__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给出的答案正确地解决了创建元类的问题,该元类继承自两个元类,然后直接使用它。

但是,如果可能的话,我会避免这种复杂的解决方案。多重继承有点难以管理 - 不是坏事,只是复杂 - 而在元类中执行它可能会导致难以掌握的事情。

这显然取决于您要完成的工作以及您记录和测试解决方案的好处。如果您想要或需要使用元类来解决它,我建议使用抽象基类(基本上是类别)。 ABC包含_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'>]

答案 1 :(得分:3)

您的DefaultResource类似乎是继承自具有两个不同元类的类:BaseRegistered(带有元类ResourceRegistry)和Resource(带有Flask&#39; s {{ 1}}元类)。

This answer会建议做类似的事情:

MethodViewType

然后像以前一样继续。

答案 2 :(得分:0)

它不必很复杂,但是您必须决定是否也应在REGISTRY中注册一些ResourceMixins(在这里它们会注册)。在python 2中,只需使用__metaclass__,我就花了几个小时,因为我没有保存代码,并且想知道这是一种什么样的法术。 在我的情况下,我想添加额外的method_decorators,但是Resource类已经声明了它,因此无法在attrs中添加它,而是在new_cls对象上进行设置。同样不要忘了调用MethodViewType .__ new__(super(),而不是类型)。

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()