19

私は Python デコレーターを初めて使用します (すばらしい機能です!)。self引数が混乱しているため、次のコードをうまく動作させることができません。

#this is the decorator
class cacher(object):

    def __init__(self, f):
        self.f = f
        self.cache = {}

    def __call__(self, *args):
        fname = self.f.__name__
        if (fname not in self.cache):
            self.cache[fname] = self.f(self,*args)
        else:
            print "using cache"
        return self.cache[fname]

class Session(p.Session):

    def __init__(self, user, passw):
        self.pl = p.Session(user, passw)

    @cacher
    def get_something(self):
        print "get_something called with self = %s "% self
        return self.pl.get_something()

s = Session(u,p)
s.get_something()

これを実行すると、次のようになります。

get_something called with self = <__main__.cacher object at 0x020870F0> 
Traceback:
...
AttributeError: 'cacher' object has no attribute 'pl'

私がする行のためにself.cache[fname] = self.f(self,*args)

問題- 明らかに問題は、実際には属性selfを持たない Session インスタンスではなく、キャッシャー オブジェクトであることです。plただし、これを解決する方法が見つかりません。

私が考えたが使用できない解決策- 正しいコンテキストで評価されるように、デコレータ クラスが値の代わりに関数を返すようにすることを考えました (この記事のセクション 2.1 のように)selfが、それは不可能です。私のデコレーターはクラスとして実装され、組み込み __call__メソッドを使用しているためです。次に、__call__メソッドが必要ないように、デコレーターにクラスを使用しないself.cacheことを考えましたが、デコレーター呼び出し間で状態を保持する必要があるため (つまり、属性の内容を追跡するため)、それはできません。.

質問- グローバルcacheディクショナリ変数を使用する以外に (試したことはありませんが、うまくいくと思います)、このデコレータを機能させる方法はありますか?

編集: この SO の質問は似ているようですDecorating python class methods, how do I pass the instance to the decorator?

4

3 に答える 3

36

次のように記述子プロトコルを使用します。

import functools

class cacher(object):

    def __init__(self, f):
        self.f = f
        self.cache = {}

    def __call__(self, *args):
        fname = self.f.__name__
        if (fname not in self.cache):
            self.cache[fname] = self.f(self,*args)
        else:
            print "using cache"
        return self.cache[fname]

    def __get__(self, instance, instancetype):
        """Implement the descriptor protocol to make decorating instance 
        method possible.

        """

        # Return a partial function with the first argument is the instance 
        #   of the class decorated.
        return functools.partial(self.__call__, instance)

編集 :

それはどのように動作しますか?

デコレーターで記述子プロトコルを使用すると、正しいインスタンスで装飾されたメソッドに self としてアクセスできるようになります。

では、次のことを行います。

class Session(p.Session):
    ...

    @cacher
    def get_something(self):
        print "get_something called with self = %s "% self
        return self.pl.get_something()

に相当:

class Session(p.Session):
    ...

    def get_something(self):
        print "get_something called with self = %s "% self
        return self.pl.get_something()

    get_something = cacher(get_something)

したがって、 get_something は cacher のインスタンスです。したがって、メソッド get_something を呼び出すと、次のように変換されます (記述子プロトコルのため)。

session = Session()
session.get_something  
#  <==> 
session.get_something.__get__(get_something, session, <type ..>)
# N.B: get_something is an instance of cacher class.

そして理由:

session.get_something.__get__(get_something, session, <type ..>)
# return
get_something.__call__(session, ...) # the partial function.

それで

session.get_something(*args)
# <==>
get_something.__call__(session, *args)

うまくいけば、これがどのように機能するかを説明します:)

于 2011-03-29T08:51:19.243 に答える
4

記述子プロトコルをいじる必要がないため、多くの場合、クロージャーの方が適しています。変更可能なオブジェクトを含むスコープに固定するだけなので、呼び出し間で変更可能な状態を保存するのは、クラスを使用するよりもさらに簡単です (変更できないオブジェクトへの参照は、nonlocalキーワードを介して処理するか、単一エントリのような変更可能なオブジェクトに格納することによって処理できます)。リスト)。

#this is the decorator
from functools import wraps
def cacher(f):
    # No point using a dict, since we only ever cache one value
    # If you meant to create cache entries for different arguments
    # check the memoise decorator linked in other answers
    print("cacher called")
    cache = []
    @wraps(f)
    def wrapped(*args, **kwds):
        print ("wrapped called")
        if not cache:
            print("calculating and caching result")
            cache.append(f(*args, **kwds))
        return cache[0]
    return wrapped

class C:
    @cacher
    def get_something(self):
        print "get_something called with self = %s "% self

C().get_something()
C().get_something()

クロージャーが機能する方法に完全に精通していない場合は、(上記のように) print ステートメントを追加するとわかりやすいでしょう。cacher関数が定義されているときにのみ呼び出されますが、wrappedメソッドが呼び出されるたびに呼び出されます。

ただし、これは、メモ化手法とインスタンス メソッドに注意する必要があることを強調していselfます。

于 2011-03-29T13:00:07.563 に答える
1

cacherまず、次の行で最初の引数としてオブジェクトを明示的に渡します。

self.cache[fname] = self.f(self,*args)

Python はself、メソッドのみの引数のリストに自動的に追加します。cacherクラスの名前空間で定義された関数 (ただし、他の callable はオブジェクトではありません!) をメソッドに変換します。このような動作を得るには、次の 2 つの方法があります。

  1. クロージャーを使用して関数を返すようにデコレーターを変更します。
  2. memoize デコレータ レシピselfで行われているように、自分で引数を渡す記述子プロトコルを実装します。
于 2011-03-29T09:19:38.653 に答える