2009-05-20 19 views
6

Lubię być w stanie zmierzyć wydajność funkcji Pythona kod mam, więc bardzo często zrobić coś podobnego do tego ...Dekoratorzy funkcyjne

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 

Tak, wiem, że mają mierzyć wydajność z timeit , ale działa to dobrze dla moich potrzeb i pozwala mi włączać i wyłączać tę informację w celu debugowania bardzo płynnie.

Ten kod oczywiście pochodzi sprzed poznania dekoratorów funkcji ... Nie, żebym teraz wiedział o nich dużo, ale myślę, że mógłbym napisać dekorator, który wykonałby następujące czynności, używając słownika ** kwds:

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

chciałbym mimo wszystko chciał powielać uprzedniej obróbce moich funkcji, tak że obróbka będzie coś więcej takich jak:

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 

myślę, że to wymagałoby dekorator policzyć liczbę argumentów, wiedzieć ile zajmie oryginalna funkcja, stri p jakikolwiek nadmiar, przekazuj odpowiednią liczbę do funkcji ... Nie jestem pewien co zrobić, aby powiedzieć Pythonie, aby to zrobić ... Czy to możliwe? Czy istnieje lepszy sposób na osiągnięcie tego samego?

Odpowiedz

9

Choć inspect może Ci trochę po drodze, co chcesz, to w ogóle niemożliwe:

def f(*args): 
    pass 

Teraz jak wiele argumentów ma f wziąć? Ponieważ *args i **kwargs pozwalają na dowolną liczbę argumentów, nie ma możliwości określenia liczby argumentów wymaganych przez funkcję. W rzeczywistości zdarzają się przypadki, w których funkcja naprawdę obsługuje tyle, ile jest wrzuconych!


Edit: jeśli jesteś gotów znosić verbose jako specjalny argumentu słowa kluczowego, można to zrobić:

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

(! Dzięki Alex Martelli za cynk kwargs.pop)

+0

ale co "some_function (arg1, arg2, ..., argN, True)" należy również uruchomić? –

+0

Cóż, wymagałoby to innego rodzaju dekoratora. Jeden, który używa inspekcji lub pobiera dodatkową wartość liczbową wskazującą ustaloną liczbę argumentów, które przyjmuje funkcja zdobiona. – Stephan202

+0

Dzięki za napisanie tego dla mnie Stephan! Myślałem o tym w drodze do domu z pracy i doszedłem do wniosku, że użycie argumentów słów kluczowych w tym celu jest lepszym podejściem, ponieważ pozwoli mi to uwzględnić inne parametry, takie jak to, ile razy czas funkcja i czy wydruk powinien odzwierciedlać średni czas, minimalny czas lub bardziej szczegółowe statystyki ... – Jaime

8

+1 na odpowiedź Stephan202, jednak (umieszczając to w oddzielnej odpowiedzi, ponieważ komentarze nie formatują dobrze kodu!), Następujący fragment kodu w tej odpowiedzi:

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

można wyrazić znacznie bardziej jasno i zwięźle jak:

verbose = kwargs.pop('verbose', False) 
+0

+1. Nigdy nie używam popu() na dyktacie, jak sądzę. – Stephan202

0

może to być trudne, ale można zrobić coś na tych liniach. Kod poniżej próbuje usunąć wszelkie dodatkowe argumenty i je wydrukuje.

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

wyjście jest

func1 has extra args=['x'] 
func1 has extra args=['y'] 
Powiązane problemy