Tengo el siguiente administrador de contexto y decorador para cronometrar cualquier función o bloque de código:
import time from contextlib import ContextDecorator class timer(ContextDecorator): def __init__(self, label: str): self.label = label def __enter__(self): self.start_time = time.perf_counter() return self def __exit__(self, *exc): net_time = time.perf_counter() - self.start_time print(f"{self.label} took {net_time:.1f} seconds") return FalsePuedes usarlo como un administrador de contexto:
with timer("my code block"): time.sleep(2) # my code block took 2.0 secondsTambién puedes usarlo como decorador:
@timer("my_func") def my_func(): time.sleep(3) my_func() # my_func took 3.0 seconds Lo único que no me gusta es tener que pasar manualmente el nombre de la función como label cuando se usa como decorador. Me encantaría que el decorador use automáticamente el nombre de la función si no se pasa ninguna etiqueta:
@timer() def my_func(): time.sleep(3) my_func() # my_func took 3.0 seconds¿Hay alguna manera de hacer esto?
Si también anula el __call__() heredado de la clase base ContextDecorator en su clase de timer y agrega un valor predeterminado único al inicializador para el argumento de la label , puede verificarlo y tomar el __name__ de la función cuando se llama:
import time from contextlib import ContextDecorator class timer(ContextDecorator): def __init__(self, label: str=None): self.label = label def __call__(self, func): if self.label is None: # Label was not provided self.label = func.__name__ # Use function's name. return super().__call__(func) def __enter__(self): self.start_time = time.perf_counter() return self def __exit__(self, *exc): net_time = time.perf_counter() - self.start_time print(f"{self.label} took {net_time:.1f} seconds") return False @timer() def my_func(): time.sleep(3) my_func() # -> my_func took 3.0 secondsSegún un examen de la fuente de ContextDecorator , no parece haber ninguna forma de que el nombre de la función envuelta pueda estar disponible para el administrador de contexto. En su lugar, puede crear su propia versión de ContextDecorator , anulando __call__
import time import functools, contextlib class _ContextDecorator(contextlib.ContextDecorator): def __call__(self, func): self.f_name = func.__name__ @functools.wraps(func) def wrapper(*args, **kwargs): with self._recreate_cm(): return func(*args, **kwargs) return wrapperUso:
class timer(_ContextDecorator): def __init__(self, label: str = None): self.f_name = label def __enter__(self): self.start_time = time.perf_counter() return self def __exit__(self, *exc): net_time = time.perf_counter() - self.start_time print(f"{self.f_name} took {net_time:.1f} seconds") return False with timer('my_func'): time.sleep(2) @timer() def my_func(): time.sleep(3) my_func()