Python Decorators
Definition
A decorator takes in a function, adds some functionality and returns it. This is similar to packing a gift. The decorator acts as a wrapper. The nature of the object that got decorated (actual gift inside) does not alter.
We can use the @ symbol along with the name of the decorator function and place it above the definition of the function to be decorated.
def make_pretty(func):
def inner():
print("I got decorated")
func()
return inner
def ordinary():
print("I am odinary")
>>> ordinary()
I am ordinary
>>> # let's decorate this ordinary function
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated
I am ordinary
In the example shown above, make_pretty() is a decorator.
@make_pretty
def ordinary():
print("I am ordinary")
is equivalent to
def ordinary():
print("I am ordinary")
ordinary = make_pretty(ordinary)
Decorating Functions with Parameters:
def smart_divide(func):
def inner(a,b):
print("I am going to divide",a,"and",b)
if b == 0:
print("Whoops! cannot divide")
return
return func(a,b)
return inner
@smart_divide
def divide(a,b):
return a/b
>>> divide(2,5)
I am going to divide 2 and 5
0.4
>>> divide(2,0)
I am going to divide 2 and 0
Whoops! cannot divide
A keen observer will notice that parameters of the nested inner() function inside the decorator is same as the parameters of functions it decorates. Taking this into account, now we can make general decorators that work with any number of parameter.
In Python, this magic is done as function(*args, **kwargs). In this way, args will be the tuple of positional arguments and kwargs will be the dictionary of keyword arguments. An example of such decorator will be.
def works_for_all(func):
def inner(*args, **kwargs):
print("I can decorate any function")
return func(*args, **kwargs)
return inner
Use Cases
1. Analytics, logging, and instrumentation
from myapp.log import logger
def log_order_event(func):
def wrapper(*args, **kwargs):
logger.info("Ordering: %s", func.__name__)
order = func(*args, **kwargs)
logger.debug("Order result: %s", order.result)
return order
return wrapper
@log_order_event
def order_pizza(*toppings):
# let's get some pizza!
2. Validation and runtime checks
Imagine this: you have a set of functions, each returning a dictionary, which (among other fields) includes a field called "summary." The value of this summary must not be more than 80 characters long; if violated, that’s an error. Here is a decorator that raises a ValueError if that happens:
def validate_summary(func):
def wrapper(*args, **kwargs):
data = func(*args, **kwargs)
if len(data["summary"]) > 80:
raise ValueError("Summary too long")
return data
return wrapper
@validate_summary
def fetch_customer_data():
# ...
@validate_summary
def query_orders(criteria):
# ...
@validate_summary
def create_invoice(params):
# ...
References
https://www.programiz.com/python-programming/decorator
https://www.oreilly.com/ideas/5-reasons-you-need-to-learn-to-write-python-decorators
A decorator takes in a function, adds some functionality and returns it. This is similar to packing a gift. The decorator acts as a wrapper. The nature of the object that got decorated (actual gift inside) does not alter.
We can use the @ symbol along with the name of the decorator function and place it above the definition of the function to be decorated.
def make_pretty(func):
def inner():
print("I got decorated")
func()
return inner
def ordinary():
print("I am odinary")
>>> ordinary()
I am ordinary
>>> # let's decorate this ordinary function
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated
I am ordinary
In the example shown above, make_pretty() is a decorator.
@make_pretty
def ordinary():
print("I am ordinary")
is equivalent to
def ordinary():
print("I am ordinary")
ordinary = make_pretty(ordinary)
Decorating Functions with Parameters:
def smart_divide(func):
def inner(a,b):
print("I am going to divide",a,"and",b)
if b == 0:
print("Whoops! cannot divide")
return
return func(a,b)
return inner
@smart_divide
def divide(a,b):
return a/b
>>> divide(2,5)
I am going to divide 2 and 5
0.4
>>> divide(2,0)
I am going to divide 2 and 0
Whoops! cannot divide
A keen observer will notice that parameters of the nested inner() function inside the decorator is same as the parameters of functions it decorates. Taking this into account, now we can make general decorators that work with any number of parameter.
In Python, this magic is done as function(*args, **kwargs). In this way, args will be the tuple of positional arguments and kwargs will be the dictionary of keyword arguments. An example of such decorator will be.
def works_for_all(func):
def inner(*args, **kwargs):
print("I can decorate any function")
return func(*args, **kwargs)
return inner
Use Cases
1. Analytics, logging, and instrumentation
from myapp.log import logger
def log_order_event(func):
def wrapper(*args, **kwargs):
logger.info("Ordering: %s", func.__name__)
order = func(*args, **kwargs)
logger.debug("Order result: %s", order.result)
return order
return wrapper
@log_order_event
def order_pizza(*toppings):
# let's get some pizza!
2. Validation and runtime checks
Imagine this: you have a set of functions, each returning a dictionary, which (among other fields) includes a field called "summary." The value of this summary must not be more than 80 characters long; if violated, that’s an error. Here is a decorator that raises a ValueError if that happens:
def validate_summary(func):
def wrapper(*args, **kwargs):
data = func(*args, **kwargs)
if len(data["summary"]) > 80:
raise ValueError("Summary too long")
return data
return wrapper
@validate_summary
def fetch_customer_data():
# ...
@validate_summary
def query_orders(criteria):
# ...
@validate_summary
def create_invoice(params):
# ...
References
https://www.programiz.com/python-programming/decorator
https://www.oreilly.com/ideas/5-reasons-you-need-to-learn-to-write-python-decorators
Comments
Post a Comment