2009-05-20 6 views
6

J'aime être en mesure de mesurer la performance des fonctions de python I de code, donc très souvent que je fais quelque chose de similaire à ce ...Fonction Décorateurs

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 

Oui, je sais que vous êtes censé mesurer la performance avec timeit , mais cela fonctionne très bien pour mes besoins, et me permet d'activer et de désactiver ces informations pour le débogage très facilement.

Ce code de cours a été d'avant, je connaissais les décorateurs de fonction ... Pas que je sache beaucoup sur eux, mais je pense que je pourrais écrire un décorateur qui a fait ce qui suit, en utilisant le ** dictionnaire de kwds:

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

Je voudrais néanmoins dupliquer le travail avant de mes fonctions, de sorte que le travail serait quelque chose comme:

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 

Je suppose que cela nécessiterait le décorateur de compter le nombre d'arguments, savoir combien la fonction originale prendra, stri P en excès, passez le bon nombre d'entre eux à la fonction ... Je ne sais pas encore comment dire à Python de le faire ... Est-ce possible? Y a-t-il un meilleur moyen d'atteindre la même chose?

Répondre

9

Bien que inspect peut vous être un peu sur le chemin, ce que vous voulez est en général pas possible:

def f(*args): 
    pass 

Maintenant, combien d'arguments ne f prendre? Depuis *args et **kwargs permettre un nombre arbitraire d'arguments, il n'y a aucun moyen de déterminer le nombre d'arguments qu'une fonction nécessite. En fait il y a des cas où la fonction gère vraiment autant qu'il y en a jeté!


Edit: si vous êtes prêt à mettre en place avec verbose comme argument de mot-clé spécial, vous pouvez le faire:

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

(! Merci Alex Martelli pour la pointe kwargs.pop)

+0

mais ce "une_fonction (arg1, arg2, ..., argN, True)" doit également exécuter? –

+0

Eh bien, cela nécessiterait un autre type de décorateur. Celui qui utilise inspecter ou prend une valeur numérique supplémentaire indiquant le nombre fixe d'arguments que prend la fonction décorée. – Stephan202

+0

Merci de l'avoir écrit pour moi Stephan! J'y pensais en rentrant du travail, et j'en suis arrivé à la conclusion que l'utilisation d'arguments par mot clé pour cela est une meilleure approche, car elle me permettra d'inclure d'autres paramètres, comme le nombre de fois où la fonction et si l'impression doit refléter le temps moyen, le temps minimum ou des statistiques plus détaillées ... – Jaime

8

+1 sur la réponse de Stephan202, cependant (mettre ceci dans une réponse séparée puisque les commentaires ne formatent pas bien le code!), Le bit de code suivant dans cette réponse:

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

peut être exprimé plus clairement et de façon concise que:

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

+1. Jamais utilisé pop() sur un dict avant, je pense. – Stephan202

0

il pourrait être difficile, mais vous pouvez faire quelque chose sur ces lignes. Le code ci-dessous essaie de supprimer tous les arguments supplémentaires et les affiche.

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

sortie est

func1 has extra args=['x'] 
func1 has extra args=['y'] 
Questions connexes