5

私は、MongoDB と呼ばれる単純なデータベース層( minimongoに触発された) の作成者であり、現在、複数のプロジェクトにいる場合はほぼ間違いなく唯一のユーザーkaleです。モデルの基本クラスでの現在の使用は__getattr__、追跡が困難なバグにつながりました。

私が遭遇した問題は、昨年 6 月に David Halterによってこのサイトで簡潔に説明されました。議論は興味深いですが、解決策は提供されませんでした。

要するに:

>>> class A(object):
...     @property
...     def a(self):
...         print "We're here -> attribute lookup found 'a' in one of the usual places!"
...         raise AttributeError
...         return "a"
...     
...     def __getattr__(self, name):
...         print "We're here -> attribute lookup has not found the attribute in the usual places!"
...         print('attr: ', name)
...         return "not a"
... 
>>> print(A().a)
We're here -> attribute lookup found 'a' in one of the usual places!
We're here -> attribute lookup has not found the attribute in the usual places!
('attr: ', 'a')
not a
>>>

この矛盾した動作は、公式の python ドキュメントを読んで期待するものではないことに注意してください。

object.__getattr__(self, name)

属性ルックアップが通常の場所で属性を見つけられなかった場合に呼び出されます (つまり、インスタンス属性ではなく、self のクラス ツリーにも見つかりません)。name は属性名です。

AttributeError(属性が「通常の場所」で見つかったかどうかを「属性ルックアップ」が知る手段であると彼らが言及してくれればうれしいです。明確にするための括弧はせいぜい不完全に思えます。)

@property実際には、記述子の Thing で AttributeError が発生した場合、プログラミング エラーによって引き起こされたバグを追跡する際に問題が発生しました。

>>> class MessedAttrMesser(object):
...     things = {
...         'one': 0,
...         'two': 1,
...     }
...     
...     def __getattr__(self, attr):
...         try:
...             return self.things[attr]
...         except KeyError as e:
...             raise AttributeError(e)
...     
...     @property
...     def get_thing_three(self):
...         return self.three
... 
>>> 
>>> blah = MessedAttrMesser()
>>> print(blah.one)
0
>>> print(blah.two)
1
>>> print(blah.get_thing_three)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in __getattr__
AttributeError: 'get_thing_three'
>>>

この場合、クラス全体を検査することで、何が起こっているかは明らかです。ただし、スタック トレースからのメッセージ に依存している場合AttributeError: 'get_thing_three'は意味がありませんget_thing_three

の目的は、kaleモデルを構築するための基本クラスを提供することです。そのため、ベース モデル コードはエンド プログラマから隠され、このようなエラーの原因を隠すことは理想的ではありません。

末端のプログラマー (せっかく@property) は、モデルに記述子を使用することを選択する場合があり、そのコードは期待どおりに機能し、失敗するはずです。

質問

AttributeErrorを定義した基本クラスを介して s を伝播させるにはどうすればよい__getattr__ですか?

4

3 に答える 3

5

基本的に、できません---または少なくとも、単純で誰にでもできる方法ではありません。あなたが指摘したようにAttributeError、属性が「通常の場所で見つかった」かどうかを判断するためにPythonが使用するメカニズムです。ドキュメントではこれについて言及されていませんが、すでにリンクされている質問に対するこの回答で説明されているように__getattr__、のドキュメントでより明確になっています。__getattribute__

そこでオーバーライド__getattribute__してキャッチすることはできますAttributeErrorが、それをキャッチした場合、それが「本当の」エラー (属性が実際に見つからなかったことを意味する) なのか、実行中にユーザー コードによって発生したエラーなのかを判断する明白な方法はありません。ルックアップ プロセス。理論的には、トレースバックを調べて特定のものを探したり、他のさまざまなハックを行って属性の存在を確認したりできますが、これらのアプローチは既存の動作よりも脆弱で危険です。

もう 1 つの可能性は、 に基づく独自の記述子を作成することですがproperty、AttributeErrors をキャッチし、それらを別のものとして再発生させます。ただし、これには builtin の代わりにこのプロパティ置換を使用する必要がありますproperty。さらに、記述子メソッド内から発生した AttributeErrors は AttributeErrors としてではなく、別のもの (置き換えたものは何でも) として伝播されることを意味します。次に例を示します。

class MyProp(property):
    def __get__(self, obj, cls):
        try:
            return super(MyProp, self).__get__(obj, cls)
        except AttributeError:
            raise ValueError, "Property raised AttributeError"

class A(object):
    @MyProp
    def a(self):
        print "We're here -> attribute lookup found 'a' in one of the usual places!"
        raise AttributeError
        return "a"

    def __getattr__(self, name):
        print "We're here -> attribute lookup has not found the attribute in the usual places!"
        print('attr: ', name)
        return "not a"

>>> A().a
We're here -> attribute lookup found 'a' in one of the usual places!
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    A().a
  File "<pyshell#6>", line 6, in __get__
    raise ValueError, "Property raised AttributeError"
ValueError: Property raised AttributeError

ここで、AttributeErrors は ValueErrors に置き換えられます。例外が属性アクセスメカニズムから「抜け出し」、次のレベルまで伝播できるようにすることだけが必要な場合は、これで問題ありません。ただし、AttributeError が発生することを予期している複雑な例外キャッチ コードがある場合、例外タイプが変更されているため、このエラーは見逃されます。

(また、この例は明らかにプロパティの getter のみを扱っており、setter は扱っていませんが、アイデアを拡張する方法は明らかです。)

このソリューションの拡張として、このMyPropアイデアをカスタムと組み合わせることができると思います__getattribute__。基本的に、カスタム例外クラスを定義しPropertyAttributeError、プロパティ置換で AttributeError を PropertyAttributeError として再発生させることができます。次に、カスタム__getattribute__で、PropertyAttributeError をキャッチし、AttributeError として再発生させることができます。基本的に、MyPropと は__getattribute__、エラーを AttributeError から別のものに変換し、それが「安全」になったら再度 AttributeError に変換することで、Python の通常の処理をバイパスする「シャント」として機能します。__getattribute__ただし、パフォーマンスに大きな影響を与える可能性があるため、これを行う価値はないと私は感じています。

ちょっとした補遺: Python バグ トラッカーでこれに関するバグが提起されており、可能な解決策に関する最近の活動があるため、将来のバージョンで修正される可能性があります。

于 2013-03-14T04:53:55.170 に答える
2

コードで何が起こるか:

クラスの最初のケースA:

>>>print(A().a)

  1. のインスタンスを作成するA
  2. 'A'インスタンスで呼び出された属性にアクセスする

A.apython は、そのデータ モデルに従って、 usingを見つけようとしobject.__getattribute__ます (カスタム を提供していないため__getattribute__)

でも :

@property
def a(self):
    print "We're here -> attribute lookup found 'a' in one of the usual places!"
    raise AttributeError # <= an AttributeError is raised - now python resorts to '__getattr__'
    return "a" # <= this code is unreachable

したがって、__getattribute__ルックアップは で終了するため、AttributeErrorあなたの を呼び出します__getattr__:

    def __getattr__(self, name):
...         print "We're here -> attribute lookup has not found the attribute in the usual places!"
...         print('attr: ', name)
...         return "not a" #it returns 'not a'

2番目のコードで何が起こるか:

でアクセスblah.get_thing_three__getattribute__ます。AttributeError で失敗 (no in ) したため、でget_thing_three検索threeしようとしましたが、これも失敗しました。優先順位が高いため、例外が発生します。things__getattr__get_thing_threethingsget_thing_three

あなたができること:

カスタム__getattribute____getattr__その両方を書く必要があります。しかし、ほとんどの場合、それではうまくいきません。また、あなたのコードを使用する他の人々は、カスタム データ プロトコルを期待しません。

さて、私はあなたにアドバイスがあります(内部で使用する大まかなmongodb ormを作成しました):__getattr__ドキュメントからオブジェクトへのマッパーの内部に依存しないでください。クラス内のドキュメントへの直接アクセスを使用します(カプセル化を損なうことはないと思います)。これが私の例です:

class Model(object):
  _document = { 'a' : 1, 'b' : 2 }
  def __getattr__(self, name): 
     r"""syntactic sugar for those who are using this class externally. 
     >>>foo = Model()
     >>>foo.a
     1"""

  @property
  def ab_sum(self):
     try :
        return self._document[a] + self._document[b]
     except KeyError:
        raise #something that isn't AttributeError
于 2013-03-14T04:54:08.377 に答える