函数装饰器

6 投票
3 回答
597 浏览
提问于 2025-04-15 11:43

我喜欢能够测量我写的Python函数的性能,所以我经常会做类似这样的事情...

import time

def some_function(arg1, arg2, ..., argN, verbose = True) :
    t = time.clock() # works best in Windows
    # t = time.time() # apparently works better in Linux

    # Function code goes here

    t = time.clock() - t
    if verbose :
        print "some_function executed in",t,"sec."

    return return_val

是的,我知道应该用timeit来测量性能,但对我来说,这样做也完全可以满足我的需求,而且在调试时可以很方便地打开和关闭这些信息。

当然,这段代码是在我知道函数装饰器之前写的……虽然现在我对装饰器也了解得不多,但我觉得我可以写一个装饰器,使用**kwds字典来实现以下功能:

some_function(arg1, arg2, ..., argN) # Does not time function
some_function(arg1, arg2, ..., argN, verbose = True) # Times function

不过,我还是想保留我之前函数的工作方式,让它的工作方式更像这样:

some_function(arg1, arg2, ..., argN) # Does not time function
some_function(arg1, arg2, ..., argN, False) # Does not time function
some_function(arg1, arg2, ..., argN, True) # Times function

我想这需要装饰器来计算参数的数量,知道原始函数需要多少个参数,去掉多余的部分,然后把正确数量的参数传递给函数……不过我不太确定怎么告诉Python去做这个……这样做可能吗?有没有更好的方法来实现同样的效果?

3 个回答

0

这可能有点难,但你可以按照这个思路来做。下面的代码尝试去掉多余的参数,并将它们打印出来。

def mydeco(func):
    def wrap(*args, **kwargs):
        """
        we want to eat any extra argument, so just count args and kwargs
        and if more(>func.func_code.co_argcount) first take it out from kwargs 
        based on func.func_code.co_varnames, else last one from args
        """
        extraArgs = []

        newKwargs = {}
        for name, value in kwargs.iteritems():
            if name in func.func_code.co_varnames:
                newKwargs[name] = value
            else:
                extraArgs.append(kwargs[name])

        diff = len(args) + len(newKwargs) - func.func_code.co_argcount
        if diff:
            extraArgs.extend(args[-diff:])
            args = args[:-diff]

        func(*args, **newKwargs)
        print "%s has extra args=%s"%(func.func_name, extraArgs)

    return wrap

@mydeco
def func1(a, b, c=3):
    pass

func1(1,b=2,c=3, d="x")
func1(1,2,3,"y")

输出结果是

func1 has extra args=['x']
func1 has extra args=['y']
8

我赞同Stephan202的回答,不过我把这个放在单独的回答里,因为评论区不太适合格式化代码!下面这段代码:

verbose = False
if 'verbose' in kwargs:
     verbose = True
     del kwargs['verbose']

可以用更清晰、更简洁的方式来表达:

verbose = kwargs.pop('verbose', False)
9

虽然inspect这个模块可以帮你了解一些情况,但一般来说,你想要的功能是可能实现的:

def f(*args):
    pass

那么,函数f需要多少个参数呢?因为*args**kwargs可以接收任意数量的参数,所以我们无法确定一个函数到底需要多少个参数。实际上,有些情况下,函数可以处理任意多的参数,完全看你传给它多少!


补充:如果你愿意接受verbose作为一个特殊的关键字参数,你可以这样做:

import time

def timed(f):
    def dec(*args, **kwargs):
        verbose = kwargs.pop('verbose', False)
        t = time.clock()

        ret = f(*args, **kwargs)

        if verbose:
            print("%s executed in %ds" % (f.__name__, time.clock() - t))

        return ret

    return dec

@timed
def add(a, b):
    return a + b

print(add(2, 2, verbose=True))

(感谢Alex Martelli提供的kwargs.pop的建议!)

撰写回答