避免使用元类继承生成的类属性 [英] Avoid inheriting generated class attributes using metaclass
问题描述
我当时正在考虑使用元类自动将子类添加到父级以进行链接".但是,从父类继承这些属性会使事情变得混乱.有避免这种情况的好方法吗?
I was thinking of automatically adding child classes to parent for "chaining" using a metaclass. However, inheriting these attributes from parent classes messes thing up. Is there a nice way to avoid this?
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...
始终是有效的.
Well, this turns out to be trickier than it seens at first -
because basically you want to have class inheritance relationship, but do not use the normal attribute lookup paths on class inheritance -
Otherwise, HTTPError, being a subclass of BaseError, for example, would always have all the attributs present in BaseError itself - Therefore,
the chain BaseError.HTTPError.HTTPError.HTTPError.HTTPError...
would always be valid.
幸运的是,Python提供了 offer 一种机制,将类注册为其他类的子类,而没有物理"继承-也就是说,它被报告为子类,但其基类中没有父类.或__mro__
-因此,在派生类(采用?)上的属性查找不会在寄养"父级中搜索属性.
Fortunately, Python does offer a mechanism to register classes as subclasses of other, without "physical" inheritance - that is, it is reported as subclass, but does not have the parent class in its bases or __mro__
- and therefore, attribute lookup on the derived class (adopted?) does not search attributes in the "foster" parent.
通过"抽象基类"或"abc",通过其ABCMeta元类和注册"方法.
This mechanism is provided through the "abstract base classes" or "abc"s, through its ABCMeta Metaclass, and "register" method.
现在,由于您实际上也可能想声明
您的类层次结构具有常规的继承语法-也就是说,
能够写class HTTPError(BaseError):
来表示新的
类从BaseError派生-您获得了实际的物理"继承.
And now, due to the fact you also probably want to declare
your class hierarchy with the normal inheritance syntax - that is,
being able to write class HTTPError(BaseError):
to indicate the new
class derives from BaseError - you get the actual "physical" inheritance.
因此,我们可以从ABCMeta类(而不是type
)继承并编写
__new__
方法,以便排除物理继承-
并且我们也将setattr
用于您希望包含在代码中的内容,并且,我们直接在元类上触发了对parentclass.register
的所需调用.
So, we can inherit from ABCMeta class (instead of type
) and write
the __new__
method so that the physical inheritance is excluded -
and we use the setattr
for containment you intended with your code as well, and also, we trigger the needed call to parentclass.register
directly on the metaclass.
(请注意,由于我们现在正在更改基类,因此需要摆弄
在元类的__new__
方法中,而不是在__init__
上:
(Note that as we are now changing the base classes, we need to fiddle
in the __new__
method of the metaclass, not on __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 the interactive mode, check if it works as you intend:
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层次结构是否真的以这种方式工作:
And then, most important of all, testing if the Exception hierarchy actually works in this way:
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而不是Python2.Python2的日子已经过去了,Python 3中有许多新功能排除您自己的使用. (此答案中的代码是Python 2/3兼容的,但当然是__metaclass__
使用情况声明的.)
A few notes: no need to inherit from both Exception
and object
explicitly - Exception
itself already inherits from object
. And, most important: whatever project you are working on, do whatever is possible to move it to Python 3.x instead of Python 2. Python 2 is with the days counted, and there are many, many new features in Python 3 you are excluding yourself of using. (The code in this answer is Python 2/3 compatible, but for the __metaclass__
usage declaration of course).
这篇关于避免使用元类继承生成的类属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!