Python装饰器内传递参数的包装函数

13

我正在尝试编写Python装饰器,但是我在理解内部包装器如何接收参数方面遇到了问题。我有以下代码:

import time

def timing_function(some_function):
    def wrapper():
        t1 = time.time()
        some_function()
        t2 = time.time()
        return "Time it took to run: " + str((t2-t1)) + "\n"
    return wrapper

@timing_function
def my_function(x):
    return x * x

my_function(6)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-fe2786a2753c> in <module>()
----> 1 my_function(6)

TypeError: wrapper() takes no arguments (1 given)

这与示例略有不同:

import time

def timing_function(some_function):

    """
    Outputs the time a function takes
    to execute.
    """

    def wrapper():
        t1 = time.time()
        some_function()
        t2 = time.time()
        return "Time it took to run the function: " + str((t2-t1)) + "\n"
    return wrapper

@timing_function
def my_function():
    num_list = []
    for x in (range(0,10000)):
        num_list.append(x)
    return "\nSum of all the numbers: " +str((sum(num_list)))


print my_function()

Time it took to run the function: 0.0

问题似乎出在 'x' 参数上。我尝试给装饰器添加 wrapper *args,但它也没有起作用。我的问题是:

  1. 在这个简单的装饰器中,允许参数的正确方法是什么?谢谢

  2. 为什么我看到的所有装饰器示例都有一个内部函数,不能将装饰器编写为一个函数吗?

谢谢


在你的包装器签名中使用*args**kwargs,这样def wrapper()就变成了def wrapper(*args, **kwargs)。这样,你可以在任何函数或方法上使用装饰器。 - Christopher Pearson
2个回答

24
  1. 如何在这个简单的包装器中传递参数?谢谢

您需要将参数从my_function传递到wrapper,即:

def wrapper(x):

如果您希望它能够更通用地处理许多其他功能,则需要这样做:

def wrapper(*args, **kwargs):

但是,你在装饰器内部的逻辑也需要能够通用地处理argskwargs

2.为什么我见过的所有装饰器示例都有一个内部函数?不能将装饰器写成一个函数吗?

因为装饰器是一个接受函数作为参数并返回作为原始函数包装器执行的函数。事实上,装饰器通常编写为三个函数:

from functools import wraps

def somedec(somearg, someopt=None):
    def somedec_outer(fn):
        @wraps(fn)
        def somedec_inner(*args, **kwargs):
            # do stuff with somearg, someopt, args and kwargs
            response = fn(*args, **kwargs)
            return response
        return somedec_inner
    return somedec_outer
为什么要这样做?你可以根据所装饰的函数类型或装饰器的不同行为方式,向装饰器传递一些信息。

为什么要这样做?你可以根据所装饰的函数类型或装饰器的不同行为方式,向装饰器传递一些信息。

@somedec(30.0, 'foobarbaz')
def somefn(a, b, c):
    return a + b + c

@somedec(15.0, 'quxjinzop')
def otherfn(d, e, f):
    return d - e - f

functools.wraps 会让被装饰的函数在 Python 解释器中看起来像原始函数。这对于日志记录和调试非常有帮助,并且是创建装饰器时使用的最佳实践。


如果timing_function只接收 (some_function) 而非 (some_function, *args, **kwargs),那么def wrapper(*args, **kwargs)如何获取它所需的参数? - codyc4321
2
因为装饰器本质上是这样做的:my_function = timing_function(my_function),所以即使看起来像是在调用my_function,实际上你正在调用wrapper,因为timing_function返回了wrapper。因此,新的my_functionwrapper,所以wrapper接收你传递给my_function的参数和关键字参数。 - mVChr
#2 更重要的是理解,谢谢大家。 - codyc4321
1
天啊,谢谢 @mVChr。我一直想知道wrapper是从哪里获取它的args和kwargs,你的评论让我大开眼界! - AbdurRehman Khan

5
您需要将arg添加到wrapper中,然后再添加到some_function中:
def timing_function(some_function):
    def wrapper(arg): 
        t1 = time.time()
        some_function(arg)
        t2 = time.time()
        return "Time it took to run: " + str((t2-t1)) + "\n"
    return wrapper

如果您想在不同的函数上使用装饰器,请使用*args和**kwargs,这也适用于不需要参数的函数:

def timing_function(some_function):
    def wrapper(*args,**kwargs):
        t1 = time.time()
        some_function(*args,**kwargs)
        t2 = time.time()
        return "Time it took to run: " + str((t2-t1)) + "\n"
    return wrapper

顺便提一下,如果您想计时代码,您可能会发现timeit模块很有用,如果您已经安装了ipython,您可以直接使用timeit your_function
为什么我看到的所有装饰器示例都有一个内部函数,你不能将装饰器编写为一个函数吗?
不行,Python中的装饰器是可调用的Python对象,用于修改函数、方法或类定义。原始对象(即将被修改的对象)作为参数传递给装饰器。装饰器返回一个修改后的对象,例如,一个绑定到定义中使用的名称的修改后的函数。更多示例在这里

1
尽管 OP 使用了单个参数,但这似乎是一个通用的计时函数,因此 def wrapper(*args, **kwargs) 可能是更合适的签名。 - krethika
1
@mehtunguh,是的,我只是举例使用OP的代码。我的网络断了,所以我无法编辑添加一个通用的例子。 - Padraic Cunningham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接