Domanda

La nostra base di codice ha alcuni decoratori che vengono ampiamente utilizzati.

Quando creo un profilo di runtime, gran parte del grafico delle chiamate sembra un vetro di un'ora; Molte funzioni chiamano una funzione (il decoratore), che quindi chiama molte funzioni. Questo è un profilo meno utili di quanto vorrei.

C'è un modo per correggere questa situazione? Rimuovere il decoratore non è un'opzione; Fornisce funzionalità necessarie.

Dopo il fatto, abbiamo preso in considerazione di spogliarci manualmente del decoratore dai dati CProfile, ma non sembra possibile, perché i dati sono riassunti in relazioni chiamanti, che distrugge la relazione Caller-> Decorator-> Calee.

È stato utile?

Soluzione

Usando qualcosa di simile al new biblioteca (o types In Python 2.6+), è possibile creare teoricamente in modo dinamico un oggetto di codice e quindi un oggetto di funzione basato su quell'oggetto di codice che aveva un nome incorporato che variava insieme alla funzione che stavi avvolgendo.

Ciò ti permetterebbe di manipolare cose profonde come <func>.__code__.co_name (che normalmente è di sola lettura).


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 è ancora usato qui per consentire il pass-through di cose come documenti, nomi di moduli, ecc.)


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

Altri suggerimenti

Immagino che non è il decoratore stesso che si ingombra la tua profilazione, ma piuttosto il funzione wrapper Creato dal decoratore. E questo sta accadendo perché tutte le funzioni wrapper hanno lo stesso nome. Per affrontare questo, basta cambiare il decoratore il nome della funzione wrapper.

def decorator(func):

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

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

Potresti anche usare functools.wraps(), ma poi il nome della funzione wrapper corrisponderà al nome della funzione che sta avvolgendo. Immagino che sarebbe ok per la profilazione.

Ora, l'oggetto del codice della funzione ha anche un nome. Python non memorizza i riferimenti alle funzioni sullo stack, solo per codificare gli oggetti, quindi se il profiler sta ottenendo il nome della funzione wrapper da un frame stack, otterrà questo nome. I wrapper definiti nel solito modo condividono l'oggetto del codice (anche se l'oggetto funzione è diverso) a meno che non si ricostruisca esplicitamente l'oggetto del codice e l'oggetto funzione per ciascuna funzione wrapper. Questo è un po 'più di lavoro e molto specifico per Cpython (potrebbe anche essere specifico per la versione). Ma ecco come potresti farlo:

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

Sia il nome della funzione che il nome dell'oggetto del codice sono impostati qui a wrapper_originalfuncname e dovrebbero quindi essere contati separatamente dalla funzione avvolta nel profiler. Potresti facilmente impostarli solo sul nome della funzione originale in modo che il loro tempo di esecuzione venga invece inserito con la funzione originale.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top