Pregunta

Nuestra base de código tiene algunos decoradores que se usan ampliamente.

Cuando creo un perfil de tiempo de ejecución, una gran parte del gráfico de llamadas parece un vidrio de una hora; Muchas funciones llaman a una función (el decorador), que luego llama a muchas funciones. Este es un perfil menos útil del que me gustaría.

¿Hay alguna forma de rectificar esta situación? Eliminar el decorador no es una opción; Proporciona la funcionalidad necesaria.

Hemos considerado eliminar manualmente el decorador de los datos de CProfile después del hecho, pero no parece posible, porque los datos se resumen en las relaciones de llamadas-> Callee, que destruye la relación de llamadas-> decorador-> Callee.

¿Fue útil?

Solución

Usando algo como el new biblioteca (o types En Python 2.6+), teóricamente podría crear dinámicamente un objeto de código y luego un objeto de función basado en ese objeto de código que tenía un nombre incorporado que variaba junto con la función que estaba envolviendo.

Eso te permitiría manipular cosas tan profundas como <func>.__code__.co_name (que normalmente es de solo lectura).


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 todavía se usa aquí para permitir el paso de cosas como documentos, nombres de módulos, 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

Otros consejos

Voy a adivinar que no es el decorador en sí el que está abarrotando tu perfil, sino el función de envoltura Creado por el decorador. Y eso está sucediendo porque todas las funciones de envoltura tienen el mismo nombre. Para abordar esto, solo haga que el decorador cambie el nombre de la función de envoltura.

def decorator(func):

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

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

También podrías usar functools.wraps(), pero luego el nombre de la función de envoltura coincidirá con el nombre de la función que está envolviendo. Supongo que estaría bien para perfilar.

Ahora, el objeto de código de la función también tiene un nombre. Python no almacena referencias a funciones en la pila, solo a los objetos de código, por lo que si el perfilador obtiene el nombre de la función de envoltura desde un marco de pila, obtendrá este nombre. Los envoltorios definidos de la manera habitual comparten el objeto de código (aunque el objeto de función es diferente) a menos que reconstruya explícitamente el objeto de código y el objeto de función para cada función de envoltura. Esto es bastante más trabajo y muy específico de CPYTHON (incluso podría ser específico de la versión). Pero así es como podrías hacerlo:

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

Tanto el nombre de la función como el nombre del objeto de código se establecen aquí en wrapper_originalfuncname y, por lo tanto, deben contarse por separado de la función envuelta en el perfilador. Podrías establecerlos fácilmente en el nombre de la función original para que su tiempo de ejecución se transmitiera con la función original.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top