2017-05-09 46 views
0

我正在考虑使用元类自动将子类添加到父项以进行“链接”。但是,从父类继承这些属性会造成问题。有没有一种很好的方法来避免这种情况?避免继承使用元类的生成的类属性

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

回答

1

好了,这真可谓是麻烦比它作用似乎在第一 - 因为基本上你想拥有类的继承关系,但不要使用类继承通常属性查找路径 - 否则,HTTPError,例如,作为BaseError的子类,将始终使所有属性存在于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__用法声明当然)。

+0

感谢您的回答。我可能是最好的选择(或者,似乎基于全球地图的解决方案也可以工作 - 作为答案发布) – root

0

一个相当幼稚的全球绘图解决方案,也似乎是工作:

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