Python Decorator
In my first few weeks with Python, I was shocked that I cloud pass a function around as a parameter, for example:
def foo():
pass
synchronized_foo = synchronized(lock)(foo)
synchronized_foo()
and there’s a better version with decorator:
@synchronized(lock)
def foo():
pass
Since I come from Java world, I immediately linked this with AOP in Java. but decorator seems so light and easy to use. as described in PEP 318 – Decorators for Functions and Methods
What?! Decorator on a decorator
def covert_to_upper_case(f):
"""
A simple decorator to covert return string upper case.
"""
def uppercase(*args, **kwargs):
print("upper stats....")
r = f(*args, **kwargs)
return r.upper()
return uppercase
def add_prefix(f):
"""
A simple decorator to add a prefix to return value
"""
def pre(*args, **kwargs):
r = f(*args, **kwargs)
return f"[prefix] {r}"
return pre
def add_prefix_and_covert_to_upper(f):
"""
A combination of `covert_to_upper_case` and `add_prefix`
"""
@covert_to_upper_case
@add_prefix
def covert(*args, **kwargs):
r = f(*args, **kwargs)
return r
# also work:
# covert = add_prefix(covert)
# covert = covert_to_upper_case(covert)
return covert
# @add_prefix
# @covert_to_upper_case
@add_prefix_and_covert_to_upper
def hello():
return "Python"
print(f"output: {hello()}")
In the above example:
@add_prefix
= add_prefix(f)
,
@add_prefix_and_covert_to_upper
= covert_to_upper_case(add_prefix(f))
in a debugger: hello
is:
<function covert_to_upper_case.<locals>.uppercase at 0x10e8da200>
hello
can still be a hello
if ` @wraps(f)` is added in the decorator, e.g.:
def covert_to_upper_case(f):
@wraps(f)
def uppercase(*args, **kwargs):
print("upper stats....")
r = f(*args, **kwargs)
return r.upper()
return uppercase
Hello is <function hello at 0x10a3bd200>
now!
@wraps
is a decorator to:
Update a wrapper function to look like the wrapped function
What about context manager as Decorator?
contextlib.ContextDecorator
A base class that enables a context manager to also be used as a decorator. Context managers inheriting from ContextDecorator have to implement enter and exit as normal exit retains its optional exception handling even when used as a decorator.
How does it work?
def __call__(self, func):
@wraps(func)
def inner(*args, **kwds):
with self._recreate_cm():
return func(*args, **kwds)
return inner
so that a context manager can be used in both way:
@mycontext()
def function():
print('The bit in the middle')
# or:
with mycontext():
print('The bit in the middle')
What about adding more arguments?
example:
def async_task(name: str):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
submit_task(target=f, args=args, kwargs=kwargs, name=name)
print(f"{name} task submitted")
return wrapper
return decorator
@async_task("my_task")
def my_task():
pass
Summary
- a decorator in Python is a function that takes a function as a parameter
- add
@wraps()
to keep the function signature unchanged