Question

Notre base de code contient quelques décorateurs largement utilisés.

Lorsque je crée un profil d'exécution, une grande partie du graphe d'appel ressemble à un sablier;de nombreuses fonctions appellent une fonction (le décorateur), qui appelle ensuite plusieurs fonctions.C'est un profil moins utile que je ne le souhaiterais.

Existe-t-il un moyen de remédier à cette situation?La suppression du décorateur n'est pas une option;il fournit les fonctionnalités nécessaires.

Nous avons envisagé de supprimer manuellement le décorateur des données cProfile après coup, mais cela ne semble pas possible, car les données sont résumées dans des relations appelant-> appelé, ce qui détruit la relation appelant-> décorateur-> appelé.

Était-ce utile?

La solution

En utilisant quelque chose comme la bibliothèque new (ou types en Python 2.6+), vous pourriez théoriquement créer dynamiquement un objet code, puis un objet fonction basé sur cet objet code qui avait un nom intégré quivariait avec la fonction que vous enveloppiez.

Cela vous permettrait de manipuler des choses aussi profondément que <func>.__code__.co_name (qui est normalement en lecture seule).


import functools
import types

def metadec(func):

    @functools.wraps(func)
    def wrapper(*args, **kwargs):   
        # do stuff
        return func(*args, **kwargs)

    c = wrapper.func_code
    fname = "%s__%s" % (func.__name__, wrapper.__name__)

    code = types.CodeType(
                c.co_argcount, 
                c.co_nlocals,
                c.co_stacksize,
                c.co_flags,  
                c.co_code,        
                c.co_consts,         
                c.co_names,
                c.co_varnames,
                c.co_filename,
                fname, # change the name
                c.co_firstlineno,
                c.co_lnotab,
                c.co_freevars,
                c.co_cellvars,
            )

    return types.FunctionType(
            code, # Use our updated code object
            wrapper.func_globals,
            fname, # Use the updated name
            wrapper.func_defaults,
            wrapper.func_closure,
        )

(functools.wraps est toujours utilisé ici afin de permettre le passage de choses comme les docstrings, les noms de modules, etc.)


In [1]: from metadec import metadec

In [2]: @metadec
   ...: def foobar(x):
   ...:     print(x)
   ...:     
   ...:     

In [3]: foobar.__name__
Out[3]: 'foobar__wrapper'

In [4]: foobar(1)
1

Autres conseils

Je suppose que ce n'est pas le décorateur lui-même qui encombre votre profilage, mais plutôt la fonction wrapper créée par le décorateur. Et cela se produit parce que toutes les fonctions de wrapper ont le même nom. Pour résoudre ce problème, demandez au décorateur de changer le nom de la fonction wrapper.

def decorator(func):

    def wrapper(*args):
        print "enter func", func.__name__
        return func(*args)

    wrapper.__name__ += "_" + func.__name__
    return wrapper

Vous pouvez également utiliser functools.wraps(), mais le nom de la fonction wrapper correspondra au nom de la fonction qu'elle encapsule. Je suppose que ce serait acceptable pour le profilage.

Maintenant, l'objet code de la fonction a également un nom. Python ne stocke pas de références aux fonctions sur la pile, uniquement aux objets de code, donc si le profileur obtient le nom de la fonction wrapper à partir d'un cadre de pile, il obtiendra ce nom. Les wrappers définis de la manière habituelle partagent l'objet code (même si l'objet fonction est différent), sauf si vous reconstruisez explicitement l'objet code et l'objet fonction pour chaque fonction wrapper. C'est un peu plus de travail et très spécifique à CPython (peut même être spécifique à la version). Mais voici comment procéder:

from types import FunctionType, CodeType    

def decorator(func):

    def wrapper(*args):
        print "enter func", func.__name__
        return func(*args)

    name = wrapper.__name__ + "_" + func.__name__

    func_code = wrapper.func_code
    new_code  = CodeType(
            func_code.co_argcount, func_code.co_nlocals, func_code.co_stacksize,
            func_code.co_flags, func_code.co_code, func_code.co_consts,
            func_code.co_names, func_code.co_varnames, func_code.co_filename,
            name, func_code.co_firstlineno, func_code.co_lnotab,
            func_code.co_freevars, func_code.co_cellvars)
    wrapper   = FunctionType(
            new_code, wrapper.func_globals, name, wrapper.func_defaults,
            wrapper.func_closure)

    return wrapper

Le nom de la fonction et le nom de l'objet code sont définis ici sur wrapper_originalfuncname et doivent donc être comptés séparément de la fonction encapsulée dans le profileur. Vous pouvez facilement les définir uniquement sur le nom de la fonction d'origine afin que leur temps d'exécution soit ajouté à celui de la fonction d'origine.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top