4

@memoize最近、StackOverflow コミュニティは、関数だけでなくメソッドやクラスも一般的な方法で、つまり、どのような種類のものを装飾するかを事前に知らなくても装飾できる、かなり簡潔なデコレーターの開発を支援してくれました。

私が遭遇した問題の 1 つは、クラスを@memoizeで装飾し、そのメソッドの 1 つを で装飾しようとすると@staticmethod、期待どおりに動作しない、つまり、まったく呼び出すことができないということClassName.thestaticmethod()です。私が思いついた元のソリューションは次のようになりました。

def memoize(obj):
    """General-purpose cache for classes, methods, and functions."""
    cache = obj.cache = {}

    def memoizer(*args, **kwargs):
        """Do cache lookups and populate the cache in the case of misses."""
        key = args[0] if len(args) is 1 else args
        if key not in cache:
            cache[key] = obj(*args, **kwargs)
        return cache[key]

    # Make the memoizer func masquerade as the object we are memoizing.
    # This makes class attributes and static methods behave as expected.
    for k, v in obj.__dict__.items():
        memoizer.__dict__[k] = v.__func__ if type(v) is staticmethod else v
    return memoizer

functools.wrapsしかし、その後、よりクリーンで完全な方法でデコレータ関数を装飾された関数として偽装することを目的としたについて学び、実際に次のように採用しました。

def memoize(obj):
    """General-purpose cache for class instantiations, methods, and functions."""
    cache = obj.cache = {}

    @functools.wraps(obj)
    def memoizer(*args, **kwargs):
        """Do cache lookups and populate the cache in the case of misses."""
        key = args[0] if len(args) is 1 else args
        if key not in cache:
            cache[key] = obj(*args, **kwargs)
        return cache[key]
    return memoizer

これは非常に良さそうに見えますが、 sfunctools.wrapsも s もまったくサポートしていません。たとえば、次のようなことを試した場合:staticmethodclassmethod

@memoize
class Flub:
    def __init__(self, foo):
        """It is an error to have more than one instance per foo."""
        self.foo = foo

    @staticmethod
    def do_for_all():
        """Have some effect on all instances of Flub."""
        for flub in Flub.cache.values():
            print flub.foo
Flub('alpha') is Flub('alpha')  #=> True
Flub('beta') is Flub('beta')    #=> True
Flub.do_for_all()               #=> 'alpha'
                                #   'beta'

@memoizeこれは、リストされた Iの最初の実装では機能しますがTypeError: 'staticmethod' object is not callable、2 番目の実装では発生します。

私は本当に、functools.wrapsその醜さを取り戻すことなく、使用するだけでこれを解決したかったので、実際に純粋な Python で__dict__自分自身を再実装しました。これは次のようになりました。staticmethod

class staticmethod(object):
    """Make @staticmethods play nice with @memoize."""

    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        """Provide the expected behavior inside memoized classes."""
        return self.func(*args, **kwargs)

    def __get__(self, obj, objtype=None):
        """Re-implement the standard behavior for non-memoized classes."""
        return self.func

そして、これは、私が知る限り、上記の 2 番目の@memoize実装と完全に連携して機能します。

それで、私の質問は次のとおりです。標準のビルトインがそれ自体で適切に動作しないのはなぜですかstaticmethod、および/またはfunctools.wrapsこの状況を予測して解決しないのはなぜですか?

これは Python のバグですか? またはでfunctools.wraps

ビルトインをオーバーライドする際の注意事項は何staticmethodですか? 私が言うように、今は問題なく動作しているようですが、私の実装と組み込みの実装の間に隠れた非互換性があり、後で爆発する可能性があるのではないかと心配しています.

ありがとう。

明確にするために編集:私のアプリケーションには、高価な検索を行い、頻繁に呼び出される関数があるため、メモしました。それは非常に簡単です。それに加えて、ファイルを表す多くのクラスがあり、ファイル システム内の同じファイルを表す複数のインスタンスを持つと、通常、一貫性のない状態になるため、ファイル名ごとに 1 つのインスタンスのみを適用することが重要です。デコレーターをこの目的に適合させ@memoize、従来のメモライザーとしての機能を維持することは本質的に簡単です。

の 3 つの異なる用途の実際の例を次に示し@memoizeます。

4

3 に答える 3

7

あなたのためのいくつかの考え:

  • staticmethodの操作は、クラスデコレータの演算子と完全に直交しています。関数をstaticmethodにすることは、属性ルックアップ中に何が起こるかにのみ影響します。クラスデコレータは、クラスのコンパイル時の変換です。

  • functools.wrapsには「バグ」はありません。関数属性をある関数から別の関数にコピーするだけです。

  • 現在書かれているように、メモ化ツールは、classmethodsとstaticmethodsの異なる呼び出しシグネチャを考慮していません。これは、クラスツール自体ではないメモ化の弱点です。

クラスデコレータ、staticmethods、classmethods、functoolsなどのツールが、ある種の相互に統合されたインテリジェンスを持つことを想像したと思います。代わりに、これらのツールはすべて非常に単純であり、プログラマーがそれらの相互作用を意識的に設計する必要があります。

ISTMの根本的な問題は、述べられた目標がやや不十分であるということです。「関数だけでなく、メソッドやクラスも一般的な方法で、つまり、どのような種類のものを装飾するかを事前に知らなくても装飾できるデコレータ。 「」

各シナリオでメモ化のセマンティクスがどのようになるかは完全には明らかではありません。そして、Pythonの単純なコンポーネントが、あなたが本当にやりたいことを推測できるような方法で自動的に自分自身を構成する方法はありません。

さまざまなオブジェクトで使用するメモ化の実際の例のリストから始めることをお勧めします。次に、現在のソリューションの構築を開始して、一度に1つずつ機能させるようにします。各ステップで、スペックがmeoizeが実際に行うものと一致しない場所を学習します。

もう1つの考えは、functools.wrapsクラスデコレータはこの問題に厳密に必要ではないということです。どちらも手動で実装できます。ツールを配線して、やりたいことを実行することから始めます。それが機能したら、ステップをラップとデコレータに置き換えることを検討してください。それは、ツールが適切でない可能性がある状況で、ツールをあなたの意志に強制しようとするよりも優れています。

お役に立てれば。

于 2012-06-24T01:58:16.927 に答える
2

クラスの装飾は、クラスの構造を潜在的に変更するために使用されます。これは一種の便利ですが、 とまったく同じではありません__new__

# Make the memoizer func masquerade as the object we are memoizing.
# This makes class attributes and static methods behave as expected.
for k, v in obj.__dict__.items():
    memoizer.__dict__[k] = v.__func__ if type(v) is staticmethod else v
return memoizer

上記のコードは、インスタンス内のメソッドにラッパーを強制しています。

class Flub:
    @memoize
    @staticmethod
    def do_things():
        print 'Do some things.'
Flub.do_things()

私はこれがあなたが使用すべきコードであるべきだと信じています - 引数を受け取らない場合、args[0] はIndexError

于 2012-06-24T02:08:34.363 に答える
2

問題は、デコレーターがクラス (つまり のインスタンスtype) を受け入れて関数を返すことです。これは (ほぼ)カテゴリの誤りに相当するプログラミングです。クラスは、(コンストラクターとして) 呼び出すことができるという点で関数のように見えるかもしれませんが、インスタンスを返す関数がそのインスタンスの型のクラスと同等であるという意味ではありません。たとえばinstanceof、正しい結果が得られる方法はなく、さらに装飾されたクラスはサブクラス化できなくなります (クラスではないためです!)。

あなたがしなければならないことは、クラスで呼び出されたときにデコレーターを検出するように調整し、その場合、必要な動作を持つラッパー クラスを (class構文を使用するか、type3 引数のコンストラクターを介して) 構築することです。それか memoize のいずれかです__new__(ただし、既に存在するインスタンスであっても、適切な型であれば__init__の戻り値で が呼び出されることに注意してください)。__new__

于 2012-06-25T00:57:10.600 に答える