关于如何通过Python装饰器传递参数的困惑

时间:2013-09-13 15:54:18

标签: python decorator python-2.x

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

@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参数接收多个值?

2 个答案:

答案 0 :(得分: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)  

答案 1 :(得分: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函数没有在精确中调用,就像调用包装器一样,因此我感到困惑。

事后看来,这更有意义,而且很可能是因为我使用了一个我不太了解的行为帮助。