我当时正在考虑自动将子类添加到父类,以使用元类进行“链接”。但是,从父类继承这些属性会使事情变得混乱。有避免这种情况的好方法吗?

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

最佳答案

好吧,事实证明,这比起初要棘手-
因为基本上您想拥有类继承关系,但不要在类继承上使用常规的属性查找路径-
否则,例如,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


并进行快速测试:

class BaseError(Exception):
    __metaclass__ = MetaError
class HTTPError(BaseError):
    pass
class HTTPBadRequest(HTTPError):
    pass


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

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'


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

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


一些注意事项:无需显式继承Exceptionobject-Exception本身已经继承了object。而且,最重要的是:无论您正在从事的项目是什么,都应尽一切可能将其移至Python 3.x而不是Python2。Python2的日子已经过去了,Python 3中有许多新功能排除自己的使用。 (此答案中的代码与Python 2/3兼容,但是当然是针对__metaclass__使用情况的声明)。

关于python - 避免使用元类继承生成的类属性,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43863619/

10-15 08:54