避免使用元类继承生成的类属性

2024-10-02 00:29:21 发布

您现在位置:Python中文网/ 问答频道 /正文

我在考虑自动添加子类到父类,以便使用元类“链接”。然而,从父类继承这些属性会把事情搞砸。有什么好办法可以避免这种情况吗?在

class MetaError(type):
    def __init__(cls, name, bases, attrs):
        for base in bases:
            setattr(base, name, cls)
        super(MetaError, cls).__init__(name, bases, attrs)

class BaseError(Exception, object):

    def __init__(self, message):
        super(BaseError, self).__init__(message)

class HttpError(BaseError):
    __metaclass__ = MetaError

class HttpBadRequest(HttpError):
    pass

class HttpNotFound(HttpError):
    pass

class FileNotFound(HttpNotFound):
    pass

class InvalidJson(HttpBadRequest):
    pass

http = HttpError

#  now I can do
raise http.HttpNotFound('Not found')
raise http.HttpNotFound.FileNotFound('File not found')
raise http.HttpBadRequest.InvalidJson('Invalid json')

#  unfortunately this also works
raise http.HttpBadRequest.HttpBadRequest('Bad request')
raise http.HttpBadRequest.HttpNotFound('Not found')

Tags: namehttpinitpassclassclsraisebases
2条回答

一个相当幼稚的全局映射解决方案似乎也在工作:

m = {}
class MetaError(type):

    def __init__(cls, name, bases, attrs):
        for base in bases:
            m[(base, name)] = cls 
        super(MetaError, cls).__init__(name, bases, attrs)

    def __getattribute__(self, value):
        if (self, value) in m:
            return m[self, value]
        return type.__getattribute__(self, value)

class BaseError(Exception):
    __metaclass__ = MetaError

class HttpError(BaseError):
    pass

class HttpBadRequest(HttpError):
    pass

class HttpNotFound(HttpError):
    pass

class FileNotFound(HttpNotFound):
    pass

class InvalidJson(HttpBadRequest):
    pass

好吧,这比一开始看起来要复杂- 因为基本上你想要有类继承关系,但是不要在类继承上使用普通的属性查找路径- 否则,HTTPError作为BaseError的一个子类,将始终拥有BaseError本身的所有属性,因此, 链BaseError.HTTPError.HTTPError.HTTPError.HTTPError...将始终有效。在

幸运的是,Python提供了一种机制,可以将类注册为其他类的子类,而不需要“物理”继承——也就是说,它被报告为子类,但它的基类或__mro__中没有父类,因此,在派生类上进行属性查找(采用了吗?)不在“寄养”父级中搜索属性。在

这个机制是通过“abstract base classes”或“abc”通过其ABCMeta元类和“register”方法提供的。在

现在,由于你可能还想申报 使用普通继承语法的类层次结构-即, 能够写class HTTPError(BaseError):来表示新的 类派生自BaseError-您可以获得实际的“物理”继承。在

因此,我们可以从ABCMeta类继承(而不是type)并编写 使物理继承被排除的__new__方法- 我们还使用setattr来包含您的代码,而且,我们直接在元类上触发对parentclass.register的必要调用。在

(请注意,由于我们现在正在更改基类,我们需要进行调整 在元类的__new__方法中,而不是在__init__上:

from abc import ABCMeta

class MetaError(ABCMeta):
    def __new__(metacls, name, bases, attrs):

        new_bases = []
        base_iter = list(reversed(bases))
        seen = []
        register_this = None
        while base_iter:
            base = base_iter.pop(0)
            if base in seen:
                continue
            seen.append(base)
            if isinstance(base, MetaError):
                register_this = base
                base_iter = list(reversed(base.__mro__))  + base_iter
            else:
                new_bases.insert(0, base)
        cls = super(MetaError, metacls).__new__(metacls, name, tuple(new_bases), attrs)
        if register_this:
            setattr(register_this, name, cls)
            register_this.register(cls)
        return cls

快速测试:

^{pr2}$

在交互模式下,检查它是否按预期工作:

In [38]: BaseError.HTTPError
Out[38]: __main__.HTTPError

In [39]: BaseError.HTTPError.HTTPError
                                     -
AttributeError                            Traceback (most recent call last)
<ipython-input-39-5d5d03751646> in <module>()
  > 1 BaseError.HTTPError.HTTPError

AttributeError: type object 'HTTPError' has no attribute 'HTTPError'

In [40]: HTTPError.__mro__
Out[40]: (__main__.HTTPError, Exception, BaseException, object)

In [41]: issubclass(HTTPError, BaseError)
Out[41]: True

In [42]: issubclass(HTTPBadRequest, BaseError)
Out[42]: True

In [43]: BaseError.HTTPError.HTTPBadRequest
Out[43]: __main__.HTTPBadRequest

In [44]: BaseError.HTTPBadRequest
                                     -
AttributeError                            Traceback (most recent call last)
<ipython-input-44-b40d65ca66c6> in <module>()
  > 1 BaseError.HTTPBadRequest

AttributeError: type object 'BaseError' has no attribute 'HTTPBadRequest'

然后,最重要的是,测试异常层次结构是否真的以这种方式工作:

In [45]: try:
   ....:     raise HTTPError
   ....: except BaseError:
   ....:     print("it works")
   ....: except HTTPError:
   ....:     print("not so much")
   ....: 
it works

注意:不需要同时从Exceptionobject显式继承-Exception本身已经从object继承。而且,最重要的是:无论你在做什么项目,尽一切可能把它移到python3.x而不是python2上。Python2是有天数的,而且Python3中有很多很多新特性,您不需要自己使用。(这个答案中的代码是python2/3兼容的,当然是针对__metaclass__用法声明)。在

相关问题 更多 >

    热门问题