2017-05-09 3 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

まあ、これが最初で、それはseensより扱いにくいことが判明 - 基本的には、クラスの継承関係を持つようにしたいが、クラスの継承に通常の属性検索パスを使用しないため、 - それ以外の場合は を、HTTPError、たとえば、BaseErrorのサブクラスであると、BaseError自体にすべての属性セットが存在します。したがって、 のチェーンBaseError.HTTPError.HTTPError.HTTPError.HTTPError...は常に有効です。それはそれは、サブクラスとして報告されているが、その塩基または__mro__で親クラスを持っていない - -

幸い、Pythonは「物理」の継承せず、提供他のサブクラスとしてクラスを登録するためのメカニズムを行い、したがって、派生クラスの属性参照(「?」)は、「里親」の親の属性を検索しません。

このメカニズムは、 "abstract base classes"または "abc"、ABCMetaメタクラス、および "register"メソッドによって提供されます。今

そして、原因事実のためにあなたはまた、おそらく通常の継承構文で あなたのクラス階層を宣言したい - つまり、 は新しい クラスを示すためにclass HTTPError(BaseError):を書き込むことができることがBaseErrorから派生した - あなたは、実際の取得"物理的な"継承。

だから、我々は(代わりにtypeの)ABCMetaクラスから継承することができ、物理的な継承は除外されるように __new__メソッドを書く - を、私たちはあなたにも、また、あなたのコードで意図封じ込めのためsetattrを使用し、私たちはparentclass.registerへの必要な呼び出しをメタクラス上で直接起動します。

(私たちは今、基本クラスを変更しているとして、我々はない__init__に、メタクラスの__new__方法で をいじる必要があることに注意してください:

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 2の代わりにPython 3.xに移動することが可能です。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 
関連する問題