defmy_decorator(func): defwrapper(): print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper
defsay_whee(): print("Whee!")
say_whee = my_decorator(say_whee)
>>> say_whee() Something is happening before the function is called. Whee! Something is happening after the function is called.
Put simply: decorators wrap a function, modifying its behavior.
Syntactic Sugar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
defmy_decorator(func): defwrapper(): print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper
@my_decorator defsay_whee(): print("Whee!")
>>> say_whee() Something is happening before the function is called. Whee! Something is happening after the function is called.
defdecorator(func): @functools.wraps(func) defwrapper_decorator(*args, **kwargs): # Do something before value = func(*args, **kwargs) # Do something after return value return wrapper_decorator
deftimer(func): """Print the runtime of the decorated function""" @functools.wraps(func) defwrapper_timer(*args, **kwargs): start_time = time.perf_counter() # 1 value = func(*args, **kwargs) end_time = time.perf_counter() # 2 run_time = end_time - start_time # 3 print(f"Finished {func.__name__!r} in {run_time:.4f} secs") return value return wrapper_timer
defdebug(func): """Print the function signature and return value""" @functools.wraps(func) defwrapper_debug(*args, **kwargs): args_repr = [repr(a) for a in args] # 1 kwargs_repr = [f"{k}={v!r}"for k, v in kwargs.items()] # 2 signature = ", ".join(args_repr + kwargs_repr) # 3 print(f"Calling {func.__name__}({signature})") value = func(*args, **kwargs) print(f"{func.__name__!r} returned {value!r}") # 4 return value return wrapper_debug
Fancy Decorators
Decorating Classes
Some commonly used decorators that are even built-ins in Python are @classmethod, @staticmethod, and @property. The @classmethod and @staticmethod decorators are used to define methods inside a class namespace that are not connected to a particular instance of that class. The @property decorator is used to customize getters and setters for class attributes. Expand the box below for an example using these decorators.
Think about this as the decorators being executed in the order they are listed. In other words, @debug calls @do_twice, which calls greet(), or debug(do_twice(greet())):
1 2 3 4 5
>>> greet("Eva") Calling greet('Eva') Hello Eva Hello Eva 'greet' returned None
Decorators With Arguments
1 2 3 4 5 6 7 8 9
defrepeat(num_times): defdecorator_repeat(func): @functools.wraps(func) defwrapper_repeat(*args, **kwargs): for _ inrange(num_times): value = func(*args, **kwargs) return value return wrapper_repeat return decorator_repeat
>>> greet("World") Hello World Hello World Hello World Hello World
you can also define decorators that can be used both with and without arguments. Most likely, you don’t need this, but it is nice to have the flexibility.
1 2 3 4 5 6 7 8
defname(_func=None, *, kw1=val1, kw2=val2, ...): # 1 defdecorator_name(func): ... # Create and return a wrapper function.