看起来比最初想象的要棘手——因为基本上你想要有类继承关系,但是不使用类继承时的普通属性查找路径——否则,例如,HTTPError作为BaseError的子类,将始终具有BaseError本身中存在的所有属性——因此,链BaseError.HTTPError.HTTPError.HTTPError.HTTPError...
将始终有效。
幸运的是,Python确实提供了一种机制,可以将类注册为其他类的子类,而无需"物理"继承——也就是说,它被报告为子类,但不在其基类或__mro__
中拥有父类——因此,在派生类(采用)上进行属性查找时不会在"寄养"父类中搜索属性。
通过"抽象基类"或"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>()
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>()
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
一些注意事项:不需要显式地从 Exception 和 object 派生,因为 Exception 本身就已经继承自 object。而且,最重要的是:无论你正在开发什么项目,都应该尽可能地将其移植到 Python 3.x 版本,而不是 Python 2。Python 2 的日子已经不多了,在 Python 3 中有很多很多新功能,如果你仍在使用 Python 2,那么你会错失很多机会。(这个答案中的代码是同时兼容 Python 2/3 的,但当然 __metaclass__ 的用法声明除外)。