2017-08-30 24 views
1

他のすべてのオブジェクトに基本クラスが使用されるシステムを構築しようとしています。すべての基本オブジェクトには内部で_fieldsという辞書があり、基本クラスの実装で情報を格納できます。__setattr__と__getattr__を組み合わせると、無限ループが発生します

基本クラスの実装は非常に簡単です:

class A(object): 
    def __init__(self, fields=dict()): 
     self._fields = fields 

クラスの実装は、そのsuper()__init__呼び出しでフィールドを設定することができます。

私が追加したいのは、@propertyデコレータを関数群全体に追加することなく、フィールドをプロパティとしてアクセスできることです。私はそうのように、基本クラスで、この目的のために__getattr__をオーバーライドしました:

class A(object): 
    def __init__(self, fields=dict()): 
     self._fields = fields 

    def __getattr__(self, name): 
     if hasattr(self, name): 
      return object.__getattribute__(self, name) 
     elif name in self._fields: 
      return self._fields.get(name) 
     else: 
      raise AttributeError 

今、このクラスの実装は次のように動作することができます:

class A_impl(A): 
    def __init__(self): 
     super(A_impl, self).__init__(
        fields=dict(
        val_1="foo", 
        val_2="", 
        val_3="bar", 
       ) 
      ) 

このクラスの実装を作成したことによって得られあなた行うためのオプション:

test = A_imp() 
print test.val_1 
print test.val_2 
print test.val_3 

を返します。

私もそうのようなクラスを変更し、@propertyデコレータを経由して、これを無効にすることができます:私はリターンのためにデータを操作することができます

class A_impl(A): 
    def __init__(self): 
     super(A_impl, self).__init__(
        fields=dict(
        val_1="foo", 
        val_2="", 
        val_3="bar", 
       ) 
      ) 

    @property 
    def val_1(self): 
     return self._fields.get('val_1') + "_getter" 

。唯一の問題は、に設定することができれば、私は記述子設定関数を実装する必要があります。これは、多くの複製作業を作成するプロパティ記述子を作成する必要があります。私がここで避けたいと思っているすべての私の分野の記述子。self._field[name] = valueをデフォルトとして選択する必要がある記述子セッター関数を手動で実装する場合、基本クラスの__setattr__関数を実装しました。基本クラスは次のようになります(__getattr__と同様)。

class A(object): 
    def __init__(self, fields=dict()): 
     self._fields = fields 

    def __getattr__(self, name): 
     if hasattr(self, name): 
      return object.__getattribute__(self, name) 
     elif name in self._fields: 
      return self._fields.get(name) 
     else: 
      raise AttributeError 

    def __setattr__(self, name, value): 
     if hasattr(self, name): 
      object.__setattr__(self, name, value) 
     elif name in self._fields: 
      self._fields[name] = value 
     else: 
      raise AttributeError 

は今、私は再び同じコード・テストを実行する場合:

test = A_imp() 
print test.val_1 
print test.val_2 
print test.val_3 

それは瞬時に無限ループにはまり、それは__setattr__で起動しますが、右後__getattr__にジャンプして、それをループし続けます。

私はこれに関するstackoverflowに関する多くの質問を読んできましたが、それを理解できませんでした。このため、このテストケースをきれいに理解するためです。うまくいけば、誰かが私のためにこれを明確にし、私が問題を解決するのを助けることができます。

+0

「hasattr」は不要です。 – o11c

答えて

3

実際にオブジェクトを取得しようとする以外の属性がオブジェクトにあるかどうかを確認する方法はありません。したがって、

hasattr(self, 'val_1') 

は実際にself.val_1にアクセスしようとします。self.val_1が通常の方法で見つからない場合は、__getattr__に戻ります。これは、無限再帰で再びhasattrを呼び出します。

hasattrは実際にRuntimeError: maximum recursion depth exceededを含む任意の例外をキャッチし、Falseを返すので、これはマニフェスト正確な方法は__getattr__コールがネストされている正確にどのように多く依存します。一部の__getattr__コールでは、object.__getattribute__ケースが発生し、AttributeErrorが発生します。いくつかはelif name in self._fields: return self._fields.get(name)ケースに当たった。 self._fieldsが存在する場合、これは値を返しますが、__setattr__では、時にはself._fieldsが存在しません!


あなた__setattr__試みが__init__self._fields割り当てを処理するために、それは__getattr__を呼び出す、hasattr(self, '_fields')を呼び出します。今度は、__getattr__の呼び出しによって2つの再帰的な__getattr__呼び出しが行われます.1つはhasattr、もう1つはelif name in self._fieldsです。 hasattrは例外をキャッチしているため、再帰呼び出しでは再帰呼び出しが指数関数的に呼び出されます。


__getattr__または__getattribute__hasattrを使用しないでください。一般に、属性アクセスを処理するメソッド内の属性にアクセスしようとするすべての試みには非常に注意してください。

+0

これについてどうすればいいですか?私はself._fieldエントリを常に提供するのではなく、自己実装の記述子よりも優先させたい。 – Yonathan

+0

@Yonathan: '__getattr__'は、通常の検索(' __getattribute__'による)が値を見つけられない場合にのみ呼び出されるので、実際に再度確認する必要はありません。一般的に属性のアクセスを「慎重に」する方法としては、特別なロジックをバイパスする必要のある属性の取得や割り当てに対して 'super().__ getattribute__'や' super().__ setattr__'を委譲することが一般的です。 – user2357112

+0

しかし、それを 'if name in self._fields'のように適用し、そうでなければそれを基底クラスのsetとget attr関数で' super()|__(set | set)attr __(self、name [、value]) '同じ再帰問題が発生します。私は "標準的な振る舞いを最初に解決し、それ以外の場合はself._fieldsをチェックする"問題をどのように解決できるかはわかりません。現在、実装された記述子の代わりに '__(get | set)attr__'関数の優先順位が取られています。申し訳ありませんが、この問題を解決する方法が不明です。 – Yonathan

関連する問題