【问题标题】:Avoid inheriting generated class attributes using metaclass避免使用元类继承生成的类属性
【发布时间】:2017-10-07 09:34:25
【问题描述】:

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

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

【问题讨论】:

    标签: python inheritance metaclass


    【解决方案1】:

    嗯,事实证明这比最初看到的要复杂 - 因为基本上你想有类继承关系,但不要在类继承上使用普通的属性查找路径 - 否则,例如,作为 BaseError 的子类的 HTTPError 将始终具有 BaseError 本身中存在的所有属性 - 因此, 链 BaseError.HTTPError.HTTPError.HTTPError.HTTPError... 将始终有效。

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

    这种机制是通过“abstract base classes”或“abc”,通过其 ABCMeta 元类和“注册”方法提供的。

    现在,由于您可能还想声明 您的类层次结构具有正常的继承语法 - 也就是说, 能够写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
    

    一些注意事项:无需从Exceptionobject 显式继承-Exception 本身已经从object 继承。而且,最重要的是:无论您正在从事什么项目,尽一切可能将其移至 Python 3.x 而不是 Python 2。Python 2 是有天数的,Python 3 中有很多很多新功能。排除自己使用。 (此答案中的代码与 Python 2/3 兼容,但当然是 __metaclass__ 使用声明)。

    【讨论】:

    • 感谢您的回答。我可能是最好的选择(或者,基于全球映射的解决方案似乎也可以工作 - 作为答案发布)
    【解决方案2】:

    一个相当幼稚的全局映射解决方案似乎也在起作用:

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-14
      • 1970-01-01
      • 2018-10-22
      • 1970-01-01
      • 1970-01-01
      • 2016-09-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多