Cómo hacer un decorador condicional en python.

Es posible decorar una función condicionalmente. Por ejemplo, quiero decorar la función foo() con una función de temporizador ( timeit ), haciendo que el análisis de rendimiento es True (consulte el código psuedo a continuación).

 if doing_performance_analysis: @timeit def foo(): """ do something, timeit function will return the time it takes """ time.sleep(2) else: def foo(): time.sleep(2) 

Los decoradores son simplemente callables que devuelven un reemplazo, opcionalmente la misma función, una envoltura o algo completamente diferente. Como tal, podrías crear un decorador condicional:

 def conditional_decorator(dec, condition): def decorator(func): if not condition: # Return the function unchanged, not decorated. return func return dec(func) return decorator 

Ahora puedes usarlo así:

 @conditional_decorator(timeit, doing_performance_analysis) def foo(): time.sleep(2) 

El decorador también podría ser una clase:

 class conditional_decorator(object): def __init__(self, dec, condition): self.decorator = dec self.condition = condition def __call__(self, func): if not self.condition: # Return the function unchanged, not decorated. return func return self.decorator(func) 

Aquí, el método __call__ desempeña el mismo papel que la función anidada decorator() devuelta en el primer ejemplo, y los parámetros dec y sobre cerrados en este caso se almacenan como argumentos en la instancia hasta que se aplica el decorador.

Un decorador es simplemente una función aplicada a otra función. Puedes aplicarlo manualmente:

 def foo(): # whatever time.sleep(2) if doing_performance_analysis: foo = timeit(foo) 

Qué tal si:

 def foo(): ... if doing_performance_analysis: foo = timeit(foo) 

Me imagino que incluso podrías envolver esto en un decorador que tomaría una bandera booleana y otro decorador, y solo aplicarías este último si la bandera está establecida en True :

 def cond_decorator(flag, dec): def decorate(fn): return dec(fn) if flag else fn return decorate @cond_decorator(doing_performance_analysis, timeit) def foo(): ... 

La respuesta de Blckknght es excelente si desea realizar la comprobación cada vez que llama a la función, pero si tiene una configuración que puede leer una vez y nunca cambia, es posible que no quiera verificarla cada vez que se llame a la función decorada. En algunos de nuestros demonios de alto rendimiento en el trabajo, he escrito un decorador que comprueba un archivo de configuración una vez cuando el archivo de Python se carga por primera vez y decide si debe envolverlo o no.

Aquí hay una muestra.

 def timed(f): def wrapper(*args, **kwargs): start = datetime.datetime.utcnow() return_value = f(*args, **kwargs) end = datetime.datetime.utcnow() duration = end - start log_function_call(module=f.__module__, function=f.__name__, start=__start__, end=__end__, duration=duration.total_seconds()) if config.get('RUN_TIMED_FUNCTIONS'): return wrapper return f 

Suponiendo que log_function_call registra su llamada en una base de datos, archivo de registro o lo que sea y que config.get (‘RUN_TIMED_FUNCTIONS’) verifica su configuración global, luego agregar el decorador @timed a una función comprobará una vez en la carga para ver si está sincronizando este servidor, entorno, etc. y si no, no cambiará la ejecución de la función en producción o en otros entornos en los que se preocupe por el rendimiento.

 use_decorator = False class myDecorator(object): def __init__(self, f): self.f = f def __call__(self): print "Decorated running..." print "Entering", self.f.__name__ self.f() print "Exited", self.f.__name__ def null(a): return a if use_decorator == False : myDecorator = null @myDecorator def CoreFunction(): print "Core Function running" CoreFunction() 

Esto es lo que funcionó para mí:

 def timeit(method): def timed(*args, **kw): if 'usetimer' not in kw: return method(*args, **kw) elif ('usetimer' in kw and kw.get('usetimer') is None): return method(*args, **kw) else: import time ts = time.time() result = method(*args, **kw) te = time.time() if 'log_time' in kw: name = kw.get('log_name', method.__name__.upper()) kw['log_time'][name] = int((te - ts) * 1000) else: print '%r took %2.2f ms' % \ (method.__name__, (te - ts) * 1000) return result return timed def some_func(arg1, **kwargs): #do something here some_func(param1, **{'usetimer': args.usetimer})