python包装函数在装饰器中接受参数

Posted

技术标签:

【中文标题】python包装函数在装饰器中接受参数【英文标题】:python wrapper function taking arguments inside decorator 【发布时间】:2015-09-03 10:48:06 【问题描述】:

我正在尝试编写 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”参数上。我尝试提供包装器 *args,但它也不起作用。我的问题是

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

    为什么我看到的所有装饰器示例总是有一个内部函数,你不能把装饰器写成一个函数吗?

谢谢

【问题讨论】:

在你的包装签名中使用*args**kwargs,所以def wrapper()变成def wrapper(*args, **kwargs)。这样,您可以在任何函数方法上使用装饰器。 【参考方案1】:
    在这个简单的包装器中允许参数的正确方法是什么?谢谢

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

def wrapper(x):

如果您希望它能够通用地处理更多功能,那么您必须执行以下操作:

def wrapper(*args, **kwargs):

但是你在装饰器中的逻辑也需要能够处理argskwargs

    为什么我看到的所有装饰器示例总是有一个内部函数,你不能把装饰器写成一个函数吗?

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

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) 将如何获得所需的参数 因为装饰器本质上是这样做的:my_function = timing_function(my_function) 所以即使看起来你在调用my_function,你实际上是在调用wrapper,因为timing_function 返回wrapper。所以新的my_functionwrapper,所以wrapper 接收你传递给my_function 的args 和kwargs。 #2 更重要的是要理解,ty all 该死,谢谢@mVChr,我一直在试图了解 wrapper 从哪里得到它的 args 和 kwargs,你的评论让我大吃一惊!【参考方案2】:

您需要将 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,这也适用于不带 args 的函数:

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 对象,用于修改函数、方法或类定义。将要修改的原始对象作为参数传递给装饰器。装饰器返回一个修改过的对象,例如一个修改过的函数,绑定到定义中使用的名字,还有更多例子here

【讨论】:

虽然 OP 使用了单个 arg,但这似乎是一个通用的计时函数,因此 def wrapper(*args, **kwargs) 可能是更合适的签名。 @mehtunguh,是的,我只是以 OP 的代码为例。我的网络坏了,所以我无法编辑以添加通用示例。

以上是关于python包装函数在装饰器中接受参数的主要内容,如果未能解决你的问题,请参考以下文章

Python - 在装饰器中获取原始函数参数

python学习笔记:装饰器2

基于 Python 类的装饰器,带有可以装饰方法或函数的参数

Python3中装饰器介绍

在 Sphinx 文档中保留包装/装饰 Python 函数的默认参数

python_如何修改装饰器中参数?