好吧,事实证明这比一开始看到的要棘手 -
因为基本上你想要具有类继承关系,但不要在类继承上使用正常的属性查找路径 -
否则,例如,HTTPError 作为 BaseError 的子类,将始终具有 BaseError 本身中存在的所有属性 - 因此,
连锁,链条BaseError.HTTPError.HTTPError.HTTPError.HTTPError...
永远有效。
幸运的是,Python 可以offer一种将类注册为其他类的子类的机制,无需“物理”继承 - 也就是说,它被报告为子类,但在其基类中没有父类或__mro__
- 因此,派生类(采用?)的属性查找不会搜索“寄养”父类中的属性。
该机制是通过“抽象基类 https://docs.python.org/3/library/abc.html” 或“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'
然后,最重要的是,测试异常层次结构是否确实以这种方式工作:
In [45]: try:
....: raise HTTPError
....: except BaseError:
....: print("it works")
....: except HTTPError:
....: print("not so much")
....:
it works
一些注意事项:不需要从两者继承Exception
and object
明确地——Exception
本身已经继承自object
。而且,最重要的是:无论您正在从事什么项目,请尽一切可能将其迁移到 Python 3.x,而不是 Python 2。Python 2 的日子已经到了,Python 3 中有很多很多新功能。排除自己使用。 (这个答案中的代码与Python 2/3兼容,但是对于__metaclass__
当然是使用声明)。