3

TL;DR: 関数デコレーターが引数を明確に指定しない場合、装飾された関数には、装飾されていない同じ関数と同じ引数要件 (引数の数と kwarg 名に関して) がありますか?

すべてのショッピング カートの ajax 呼び出しを処理する Web アプリのコントローラーがあります。各呼び出しの開始時に、ショッピング カート機能の実際のロジックを処理するオブジェクトを初期化します。(これは最も効率的な方法ではないかもしれませんが、私の質問は実際のショッピング カートのビットとは何の関係もありません。)

コードは次のようになります。

from webapp import request

from shopping_cart import Cart
from decorators import decorator

@decorator
def init_cart(f, *args, **kwargs):
    shopping_cart = Cart()
    return f(shopping_cart = cart)

@init_cart
def add_item(shopping_cart = None):
    shopping_cart.add(request.params)

@init_cart
def remove_item(shopping_cart = None):
    shopping_cart.remove(request.params)

などなど。

そのコードは、複数のアプリによってインポートされて呼び出される集中モジュールに存在します。個々のアプリのコードは次のようになります。

from sharedlib.controllers import cart
from app.base import *

class CartController(BaseController):

    def index(self, url = None):
        set_content_type('text/javascript')
        controller_method = getattr(cart, url)
        if controller_method:
            return controller_method()
        else:
            abort(404)

私の質問は次のとおりです。

init_cart デコレーターが受け取った引数を、呼び出されたコントローラー メソッドに渡したい場合は、cart モジュールを渡すだけでなく、次のようにします。

@decorator
def init_cart(f, *args, **kwargs):
    shopping_cart = Cart()
    kwargs['shopping_cart'] = cart
    return f(*args, **kwargs)

@init_cart
def add_item(shopping_cart = None):
    shopping_cart.add(request.params)

しかし、例外がスローされ、TypeError: add_item() got multiple values for keyword argument 'shopping_cart'

私はこの振る舞いを完全には理解していません: kwargs は確かに、'shopping_cart' のキーと値のペアを 1 つしか持っていません。

さらに、これを試してみると:

@decorator
def init_cart(f, *args, **kwargs):
    shopping_cart = Cart()
    return f(cart)

@init_cart
def add_item(shopping_cart = None):
    shopping_cart.add(request.params)

エラーが発生しますTypeError: add_item() takes exactly 1 argument (0 given)

私は、引数の受け取りと受け渡しに関して、デコレーターがどのように振る舞うかをどうにか理解していないと仮定していinit_cartます(*args, **kwargs). そうではないようです。

さらに、前の例では、どのようにしてメソッドがshopping_cart引数に対して複数の値を受け取ることができるのでしょうか?

4

2 に答える 2

0

Joran の答えは部分的に正しいことがわかりました。手動で記述したときのデコレータの理解は間違っていませんでしたが、decorator.decoratorデコレータを使用して作成を容易にするときにデコレータが何をしているのかを完全に理解できませんでした。

これは主に、decoratorモジュールがデコレートしている関数の argspec を決定しようとする方法に関係しています。この動作を示す最も簡単な方法は、 で作成されたデコレータと@decorator.decorator手動で作成されたデコレータを比較することです (別のオブジェクトの受け渡しは、実験目的で文字列の受け渡しに置き換えられています)。

import decorator

@decorator.decorator
def init_cart(f, *args, **kwargs):
    kwargs['shopping_cart'] = 'cart'
    print args, kwargs
    return f(*args, **kwargs)

@init_cart
def add_item(shopping_cart = None):
    print shopping_cart


def init_cart2(f):
    def wrapped(*args, **kwargs):
        kwargs['shopping_cart'] = 'cart'
        print args, kwargs
        return f(*args, **kwargs)
    return wrapped


@init_cart2
def add_item2(shopping_cart = None):
    print shopping_cart

したがってadd_item2、常に手動デコレーターadd_itemを使用し、モジュールのヘルパーを使用しdecoratorます。

add_item(shopping_cart = 'some value')どちらの場合も、呼び出すと常に出力されると予想していました'cart'(どちらの場合も、引数はキーワード引数であり、装飾関数によって上書きされます)。代わりに、次の結果が得られます (両方のデコレーターが、ラップされた関数に送信する引数を出力します)。

>>> add_item(shopping_cart = 'some_value')
('some_value',) {'shopping_cart': 'cart'}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 2, in add_item
  File "<stdin>", line 5, in init_cart
TypeError: add_item() got multiple values for keyword argument 'shopping_cart'

と:

>>> add_item2(shopping_cart = 'some_value')
() {'shopping_cart': 'cart'}
cart

最初の例でinit_cartは、キーワード引数として引数を受け取りません。

モジュールのコードを見ると、そのdecorator理由がより明確になります。

def decorator(caller, func=None):
    """
    decorator(caller) converts a caller function into a decorator;
    decorator(caller, func) decorates a function using a caller.
    """
    if func is None: # returns a decorator
        fun = FunctionMaker(caller)
        first_arg = inspect.getargspec(caller)[0][0]
        src = 'def %s(%s): return _call_(caller, %s)' % (
            caller.__name__, first_arg, first_arg)
        return fun.make(src, dict(caller=caller, _call_=decorator),
                        undecorated=caller)
    else: # returns a decorated function
        fun = FunctionMaker(func)
        src = """def %(name)s(%(signature)s):
    return _call_(_func_, %(signature)s)"""
        return fun.make(src, dict(_func_=func, _call_=caller), undecorated=func)

ラップされたinit_cart関数は、ラッパーが呼び出されたのとまったく同じ方法で呼び出されていないよう見えるため、混乱しています。

後から考えると、それははるかに理にかなっており、おそらく、私がよく理解していない動作のヘルパーを使用することで得られるものです。

于 2013-10-17T09:30:23.553 に答える