2013-03-22 9 views
5

記述子/デコレータを入れ子にしようとすると何が起こるのか分かりにくいです。私はPython 2.7を使用しています。Pythonで記述子/デコレータを入れ子にする

たとえば、のはpropertyclassmethod次の単純化されたバージョンを見てみましょう:

class MyProperty(object): 
    def __init__(self, fget): 
     self.fget = fget 
    def __get__(self, obj, objtype=None): 
     print 'IN MyProperty.__get__' 
     return self.fget(obj) 

class MyClassMethod(object): 
    def __init__(self, f): 
     self.f = f 
    def __get__(self, obj, objtype=None): 
     print 'IN MyClassMethod.__get__' 
     def f(*args, **kwargs): 
      return self.f(objtype, *args, **kwargs) 
     return f 

が巣にそれらをしよう:

class A(object): 
    # doesn't work: 
    @MyProperty 
    @MyClassMethod 
    def klsproperty(cls): 
     return 555 
    # works: 
    @MyProperty 
    def prop(self): 
     return 111 
    # works: 
    @MyClassMethod 
    def klsmethod(cls, x): 
     return x**2 

% print A.klsproperty 
IN MyProperty.__get__ 
... 
TypeError: 'MyClassMethod' object is not callable 

インナー記述MyClassMethod__get__メソッドが呼び出さ取得されていません。 理由を把握するために失敗すると、私は(私はと思われるもの)ノーオペレーション記述子を中に投げてみました:

の入れ子にノーオペレーション記述子/デコレータを使用しようと
class NoopDescriptor(object): 
    def __init__(self, f): 
     self.f = f 
    def __get__(self, obj, objtype=None): 
     print 'IN NoopDescriptor.__get__' 
     return self.f.__get__(obj, objtype=objtype) 

class B(object): 
    # works: 
    @NoopDescriptor 
    @MyProperty 
    def prop1(self): 
     return 888 
    # doesn't work: 
    @MyProperty 
    @NoopDescriptor 
    def prop2(self): 
     return 999 

% print B().prop1 
IN NoopDescriptor.__get__ 
IN MyProperty.__get__ 
888 
% print B().prop2 
IN MyProperty.__get__ 
... 
TypeError: 'NoopDescriptor' object is not callable 

なぜB().prop1が動作し、B().prop2は理解できません。

質問:私が間違っているのは何

  1. object is not callableエラーが発生するのはなぜですか?
  2. 正しい方法はありますか?例えばデコレータは、デコレータは、それがそのように飾る機能が呼び出され、パラメータを指定せずに使用した場合MyClassMethod及びMyProperty(又はclassmethodproperty)この場合
+1

これを直感的に理解するための鍵は、 '@ MyProperty'がメソッドではなく_data_属性のように動作するものを作成するため、メソッドディスクリプタでネストすることができないということです。その直感的な理解だけがあなたを今までに導きます。完全な話はisedevの答えです。 – abarnert

答えて

3

あなたはMyPropertyはそのラップされたオブジェクトに記述プロトコルを適用させる場合は、コードの作業を行うことができます。

class MyProperty(object): 
    def __init__(self, fget): 
     self.fget = fget 
    def __get__(self, obj, objtype=None): 
     print('IN MyProperty.__get__') 
     try: 
      return self.fget.__get__(obj, objtype)() 
     except AttributeError: # self.fget has no __get__ method 
      return self.fget(obj) 

今、あなたの例のコードは動作します:

class A(object): 
    @MyProperty 
    @MyClassMethod 
    def klsproperty(cls): 
     return 555 

print(A.klsproperty) 

出力は次のとおりです。

IN MyProperty.__get__ 
IN MyClassMethod.__get__ 
555 
+0

ありがとう、これは理にかなっています。しかしこれは推奨されるアプローチですか?もしそうなら、組み込みの 'property'と' classmethod'はこのように振る舞いません(入れ子にできません)? – shx2

+0

これが推奨されていないのか、組み込みの 'property'がこれをまだしていないのかわかりません。私はこれが解決しないいくつかの制限があることを知っています。 1つは、あなたが「MyClassMethod」をより巧妙にしたとしても、記述子を逆順に入れ子にすることはできません。そして、私は '__set__'記述子関数がクラス変数の代入のために呼び出されているとは思わないので、書き込み可能なクラスプロパティが動作するためにはメタクラスのマジックを行う必要があります。おそらく、記述子プログラミングの経験が豊富な人は、他の問題や限界に重点を置く可能性があります。 – Blckknght

5

を再利用しながら、何がMyClassPropertyを定義するための最良の方法でありますパラメータ。デコレータの戻り値は、装飾された関数の代わりに使用されます。だから、:

@MyProperty 
def prop(self): 
    ... 

は同等です:

def prop(self): 
    ... 
prop = MyProperty(prop) 

MyPropertyが記述プロトコルを実装しているため、A.propへのアクセスは、実際にA.prop.__get__()を呼び出します、そしてあなたがこの中に飾られたオブジェクトを(呼び出すために__get__を定義しましたケース、元の関数/メソッド)ので、すべて正常に動作します。

さて、ネストされた場合:

@MyProperty 
@MyClassMethod 
def prop(self): 
    ... 

同等である:

def prop(self): 
    ... 
prop = MyClassMethod(prop) # prop is now instance of MyClassMethod 
prop = MyProperty(prop)  # prop is now instance of MyProperty 
          # (with fget == MyClassMethod instance) 

さて、以前のように、A.propへのアクセスは、実際に(MyPropertyで)A.prop.__get__()を呼び出します、その後コールしようとしますMyClassMethodのインスタンス(fget属性で装飾され格納されたオブジェクト)。

ただし、MyClassMethodには__call__メソッドが定義されていないため、エラーMyClassMethod is not callableが返されます。


そして、あなたの2つ目の質問に対処するには:プロパティは、すでにクラス属性である - あなたの例では、クラスオブジェクトでプロパティの値を返しますA.propにアクセスし、A().propは、内のプロパティの値を返します。インスタンスオブジェクト(インスタンスがインスタンスをオーバーライドしなかった場合は、クラスオブジェクトと同じにすることができます)。

+0

あなたの説明は明らかです。ありがとうございました。 2番目の答えについて:私はあなたが何を意味するのか分かりません。私は 'classmethod'と並行して何かを探しています。つまり、' A.prop'または 'A()。prop'のどちらかと呼ぶことができます。どちらの場合でも、基礎となる関数には' cls' argが渡されます。 – shx2

+0

その場合、 'obj'引数の型をチェックすることができます:クラスインスタンスであれば、それからクラスを抽出し、それをインスタンスオブジェクトではなく装飾された関数に渡すことができます。 – isedev

+0

あなたはポイントを見逃しています...私の質問は 'classproperty'デコレータを実装する方法ではありません。ディスクリプタのネストについてです。あなたが提案するロジックは 'classmethod'で既に実装されています。私は、それらのロジックを再実装するのではなく、 'property'と' classmethod'を再利用/組み合わせてクリーンな方法で実現することが期待されます。しかし、私はそこにはないと思っています... – shx2

0

Graham Dumplの私自身の古い質問に対する決定的な答えが見つかりました。エルトンの魅惑的なblog。要するに

、その__get__()を呼び出すことによって、私が直接ラップ機能/オブジェクトを呼び出すために試みることによって、ディスクリプタプロトコルを尊重し、代わりの最初のそれらに彼らの「記述子の魔法」を実行する機会を与えていない書いたデコレータ(最初)。

関連する問題