3

TL;DR:如果函数装饰器没有具体指定参数,装饰函数是否具有与未装饰的相同函数的参数要求(根据参数计数和 kwarg 名称)?

我有一个用于处理所有购物车 ajax 调用的网络应用程序的控制器。在每次调用开始时,它都会初始化一个对象,该对象处理购物车功能的实际逻辑。(这可能不是最有效的方法,但我的问题与实际的购物车位无关。)

代码如下所示:

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 装饰器将它接收到的参数传递给被调用的控制器方法,除了传递购物车模块之外,我还会尝试:

@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”的键/值对。

此外,如果我尝试这个:

@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

1
def this_is_a_decorator(fn):
    def decorated_fn(*args,**kwargs):
        result = fn(*args,**kwargs) #notice we are calling the original function
        return "%s decorated"%result
    return decorated_fn

@this_is_a_decorator
def reverse_string(msg=""):
    return msg[::-1]

print reverse_string("Hello!")

为你的装饰师

def init_cart(fn):
    def decorated_fn(*args,**kwargs):
        cart = Cart()
        fn(cart)
        return cart
    return decorated_fn

@init_cart
def add_item(shopping_cart=None):
     shopping_cart.add(request.params)  
于 2013-09-13T16:24:44.637 回答
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 回答