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

查看:68
本文介绍了避免使用元类继承生成的类属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我当时正在考虑使用元类自动将子类添加到父级以进行链接".但是,从父类继承这些属性会使事情变得混乱.有避免这种情况的好方法吗?

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

一些注意事项:无需显式继承Exceptionobject-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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆