Функциональные декораторы - PullRequest
6 голосов
/ 20 мая 2009

Мне нравится иметь возможность измерять производительность кодируемых мной функций 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

Да, я знаю, что вы должны измерять производительность по времени, но это отлично работает для моих нужд и позволяет мне включать и выключать эту информацию для отладки очень плавно.

Этот код, конечно, был до того, как я узнал о декораторах функций ... Не то чтобы я знал о них сейчас много, но я думаю, что мог бы написать декоратор, который делал следующее, используя словарь ** 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 ]

9 голосов
/ 20 мая 2009

Хотя проверка может немного помочь вам, в общем, то, что вы хотите, в общем случае не возможно:

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))

(Спасибо Алекс Мартелли за подсказку kwargs.pop!)

8 голосов
/ 20 мая 2009

+ 1 в ответе Stephan202, однако (поместив его в отдельный ответ, поскольку комментарии плохо форматируют код!), Следующий фрагмент кода в этом ответе:

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

можно выразить гораздо яснее и лаконичнее:

verbose = kwargs.pop('verbose', False)
0 голосов
/ 20 мая 2009

это может быть сложно, но вы можете что-то сделать в этих строках. Приведенный ниже код пытается удалить любые дополнительные аргументы и выводит их.

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']
...