18

私はまだ Python のデコレータについて理解していません。

コーディングで関数やクラスをカスタマイズするなどのことを行うために、すでに多くのクロージャを使用し始めています。

例えば。

class Node :
    def __init__(self,val,children) :
        self.val = val
        self.children = children

def makeRunner(f) :
    def run(node) :
        f(node)
        for x in node.children :
            run(x)
    return run

tree=Node(1,[Node(2,[]),Node(3,[Node(4,[]),Node(5,[])])])

def pp(n) : print "%s," % n.val
printTree = makeRunner(pp)
printTree(tree)

私が見る限り、デコレータは似たようなことをするための別の構文です。

それ以外の

def pp(n) : print "%s," % n.val
printTree = makeRunner(pp)

私は書くだろう:

@makeRunner
def printTree(n) : print "%s," % n.val

デコレータはこれだけですか?それとも、私が見逃した根本的な違いはありますか?

4

4 に答える 4

14

構文的にはデコレータは単なる「砂糖」であるのは事実ですが、それはそれらについて考える最良の方法ではありません。

デコレータを使用すると、実際にコードを変更することなく、既存のコードに機能を組み込むことができます。そして、それらは宣言的な方法でそれを行うことを可能にします。

これにより、デコレータを使用してアスペクト指向プログラミング(AOP)を実行できます。したがって、1つの場所にカプセル化したいという横断的関心事がある場合は、デコレータを使用する必要があります。

典型的な例は、関数のエントリまたは終了、あるいはその両方をログに記録するロギングです。デコレータを使用することは、(メソッドの開始時または終了時に)ジョインポイントにアドバイス(これをログに記録する!)を適用することと同じです。

メソッドデコレーションは、OOPやリスト内包表記のような概念です。ご指摘のとおり、これは必ずしも適切であるとは限らず、使いすぎる可能性があります。しかし、適切な場所では、コードをよりモジュール化して分離するのに役立ちます。

于 2008-12-02T21:03:22.920 に答える
9

あなたの例は実際のコードですか、それとも単なる例ですか?

それらが実際のコードである場合、おそらくあなたのバックグラウンド (つまり、他のプログラミング言語に慣れている) が原因で、デコレータを使いすぎていると思います。

ステージ 1: デコレーターを避ける

def run(rootnode, func):
    def _run(node): # recursive internal function
        func(node)
        for x in node.children:
            _run(x) # recurse
    _run(rootnode) # initial run

この run メソッドは makeRunner を廃止します。あなたの例は次のようになります。

def pp(n): print "%s," % n.val
run(tree, pp)

ただし、これはジェネレーターを完全に無視するので…</p>

ステージ 2: ジェネレーターの使用

class Node :
    def __init__(self,val,children) :
        self.val = val
        self.children = children

    def __iter__(self): # recursive
        yield self
        for child in self.children:
            for item in child: # recurse
                yield item

def run(rootnode, func):
    for node in rootnode:
        func(node)

あなたの例は残っています

def pp(n): print "%s," % n.val
run(tree, pp)

特別なメソッド__iter__により、構造を使用できることに注意してくださいfor node in rootnode:。気に入らない場合は、 メソッドの名前__iter__を egwalkerに変更し、runループを次のように変更します。for node in rootnode.walker():
runclass Node

run(tree, func)ご覧のとおり、それらを name にバインドする代わりに直接使用することをお勧めしますが、デコレーターで使用するか、関数printTreeを使用することができます。functools.partial

printTree= functools.partial(run, func=pp)

そしてそれ以降、あなたはただ

printTree(tree)
于 2008-10-19T00:34:16.660 に答える
5

一般的な意味でのDecoratorsは、別のオブジェクトをラップし、オブジェクトを拡張または装飾する関数またはクラスです。デコレーターは、ラップされた関数またはオブジェクトと同じインターフェイスをサポートするため、受信者はオブジェクトが装飾されていることさえ知りません。

クロージャーは、スコープ外のパラメーターまたはその他の変数を参照する無名関数です。

したがって、基本的にデコレータークロージャーを使用し、それらを置き換えません。

def increment(x):
    return x + 1

def double_increment(func):
    def wrapper(x):
        print 'decorator executed'
        r = func(x)   # --> func is saved in __closure__
        y = r * 2
        return r, y
    return wrapper

@double_increment
def increment(x):
    return x + 1

>>> increment(2)
decorator executed
(3, 6)

>>> increment.__closure__
(<cell at 0x02C7DC50: function object at 0x02C85DB0>,)

>>> increment.__closure__[0].cell_contents 
<function increment at 0x02C85DB0>

したがって、デコレーターは元の関数をクロージャーで保存します。

于 2017-08-24T15:50:40.133 に答える
2

Dutch Master の AOP リファレンスをたどると、装飾された関数/メソッドの動作を変更するためにパラメーターを追加し始めるときに、デコレーターの使用が特に役立つことがわかります。関数定義の上にあることを読むと、はるかに簡単になります。

私が覚えているあるプロジェクトでは、大量のセロリ タスクを監視する必要があったため、デコレータを使用して必要に応じてプラグアンド調整するというアイデアを思いつきました。それは次のようなものでした。

class tracked_with(object):
    """
    Method decorator used to track the results of celery tasks.
    """
    def __init__(self, model, unique=False, id_attr='results_id',
                 log_error=False, raise_error=False):
        self.model = model
        self.unique = unique
        self.id_attr = id_attr
        self.log_error = log_error
        self.raise_error = raise_error

    def __call__(self, fn):

        def wrapped(*args, **kwargs):
            # Unique passed by parameter has priority above the decorator def
            unique = kwargs.get('unique', None)
            if unique is not None:
                self.unique = unique

            if self.unique:
                caller = args[0]
                pending = self.model.objects.filter(
                    state=self.model.Running,
                    task_type=caller.__class__.__name__
                )
                if pending.exists():
                    raise AssertionError('Another {} task is already running'
                                         ''.format(caller.__class__.__name__))

            results_id = kwargs.get(self.id_attr)
            try:
                result = fn(*args, **kwargs)

            except Retry:
                # Retry must always be raised to retry a task
                raise

            except Exception as e:
                # Error, update stats, log/raise/return depending on values
                if results_id:
                    self.model.update_stats(results_id, error=e)
                if self.log_error:
                    logger.error(e)
                if self.raise_error:
                    raise
                else:
                    return e

            else:
                # No error, save results in refresh object and return
                if results_id:
                    self.model.update_stats(results_id, **result)
                return result

        return wrapped

run次に、次のように、各ケースに必要なパラメーターを使用して、タスクのメソッドを単純に装飾しました。

class SomeTask(Task):

    @tracked_with(RefreshResults, unique=True, log_error=False)
    def run(self, *args, **kwargs)...

次に、タスクの動作を変更する (または追跡を完全に削除する) ことは、1 つのパラメーターを微調整するか、装飾された行をコメントアウトすることを意味しました。実装は非常に簡単ですが、さらに重要なのは、検査時に非常に理解しやすいことです。

于 2014-10-29T09:01:05.860 に答える