避免使用元类继承生成的类属性(Avoid inheriting generated class attributes using metaclass)

编程入门 行业动态 更新时间:2024-10-25 12:26:21
避免使用元类继承生成的类属性(Avoid inheriting generated class attributes using metaclass)

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

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

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...将始终有效。

幸运的是,Python确实提供了一种机制来将类注册为其他类的子类,而没有“物理”继承 - 也就是说,它被报告为子类,但在其基础或__mro__中没有父类 - 因此,在派生类(采用?)不搜索“寄养”父级中的属性。

这种机制通过“ 抽象基类 ”或“abc”,通过其ABCMeta元类和“寄存器”方法提供。

现在,由于您可能还希望使用正常的继承语法声明您的类层次结构 - 也就是说,能够编写class HTTPError(BaseError):指示新类派生自BaseError - 您将获得实际的“物理” “继承。

因此,我们可以继承ABCMeta类(而不是type )并编写__new__方法,以便排除物理继承 - 我们也使用setattr来控制你的代码,并且我们也会触发所需的调用parentclass.register直接在元类上。

(注意,因为我们现在正在更改基类,所以我们需要__new__元类的__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

一些注意事项:不需要显式地继承Exception和object - Exception本身已经从object继承。 而且,最重要的是:无论你正在做什么项目,做任何可能的事情都可以将它移到Python 3.x而不是Python 2.Python 2可以计算日期,并且Python 3中有很多很多新功能你是不包括使用。 (这个答案中的代码与Python 2/3兼容,但当然对于__metaclass__用法声明)。

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.

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.

This mechanism is provided through the "abstract base classes" or "abc"s, through its ABCMeta Metaclass, and "register" method.

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.

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.

(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

And for a quick test:

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'

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

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

更多推荐

本文发布于:2023-08-05 21:05:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1438342.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:类属   inheriting   Avoid   generated   attributes

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!