避免使用 metaclass 继承生成的 class 属性
Avoid inheriting generated class attributes using metaclass
我正在考虑使用元类自动将子项 类 添加到 "chaining" 的父项。但是,从父 类 继承这些属性会把事情搞砸。有什么好的方法可以避免这种情况吗?
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')
好吧,事实证明这比一开始看到的要棘手 -
因为基本上你想要 class 继承关系,但不要在 class 继承上使用正常的属性查找路径 -
否则,HTTPError,例如,作为 BaseError 的子 class,将始终具有 BaseError 本身中存在的所有属性 - 因此,
链 BaseError.HTTPError.HTTPError.HTTPError.HTTPError...
将始终有效。
幸运的是,Python 提供 一种机制,可以将 class 注册为其他 class 的子 class,无需 "physical" 继承 - 也就是说,它被报告为子 class,但在其基础或 __mro__
中没有父 class - 因此,对派生 [=63= 的属性查找](采用?)不在 "foster" 父级中搜索属性。
此机制是通过“abstract base classes”或 "abc"s、其 ABCMeta Metaclass 和 "register" 方法提供的。
现在,由于您可能还想声明
您的 class 层次结构具有正常的继承语法 - 也就是说,
能够写 class HTTPError(BaseError):
来表示新的
class 派生自 BaseError - 你得到实际的 "physical" 继承。
所以,我们可以从 ABCMeta class(而不是 type
)继承并编写
__new__
方法,以便排除物理继承 -
我们也使用 setattr
来包含您的代码,并且我们直接在 metaclass 上触发对 parentclass.register
的所需调用。
(请注意,由于我们现在正在更改基数 classes,因此我们需要 fiddle
在 metaclass 的 __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
和 object
- Exception
本身已经继承自 object
。而且,最重要的是:无论你在做什么项目,尽一切可能将它移动到 Python 3.x 而不是 Python 2. Python 2 是计算的天数,并且 Python 3 中有很多很多新功能,您排除自己使用。 (此答案中的代码是 Python 2/3 兼容的,但当然是 __metaclass__
用法声明)。
一个相当简单的全局映射解决方案,似乎也有效:
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
我正在考虑使用元类自动将子项 类 添加到 "chaining" 的父项。但是,从父 类 继承这些属性会把事情搞砸。有什么好的方法可以避免这种情况吗?
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')
好吧,事实证明这比一开始看到的要棘手 -
因为基本上你想要 class 继承关系,但不要在 class 继承上使用正常的属性查找路径 -
否则,HTTPError,例如,作为 BaseError 的子 class,将始终具有 BaseError 本身中存在的所有属性 - 因此,
链 BaseError.HTTPError.HTTPError.HTTPError.HTTPError...
将始终有效。
幸运的是,Python 提供 一种机制,可以将 class 注册为其他 class 的子 class,无需 "physical" 继承 - 也就是说,它被报告为子 class,但在其基础或 __mro__
中没有父 class - 因此,对派生 [=63= 的属性查找](采用?)不在 "foster" 父级中搜索属性。
此机制是通过“abstract base classes”或 "abc"s、其 ABCMeta Metaclass 和 "register" 方法提供的。
现在,由于您可能还想声明
您的 class 层次结构具有正常的继承语法 - 也就是说,
能够写 class HTTPError(BaseError):
来表示新的
class 派生自 BaseError - 你得到实际的 "physical" 继承。
所以,我们可以从 ABCMeta class(而不是 type
)继承并编写
__new__
方法,以便排除物理继承 -
我们也使用 setattr
来包含您的代码,并且我们直接在 metaclass 上触发对 parentclass.register
的所需调用。
(请注意,由于我们现在正在更改基数 classes,因此我们需要 fiddle
在 metaclass 的 __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
和 object
- Exception
本身已经继承自 object
。而且,最重要的是:无论你在做什么项目,尽一切可能将它移动到 Python 3.x 而不是 Python 2. Python 2 是计算的天数,并且 Python 3 中有很多很多新功能,您排除自己使用。 (此答案中的代码是 Python 2/3 兼容的,但当然是 __metaclass__
用法声明)。
一个相当简单的全局映射解决方案,似乎也有效:
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