Recently I stumbled upon a slight difference in how a Python decorator behaves depending on whether functools wraps is used or not.
Without functools, this would be an example (trivial) decorator that just takes a single argument and prints it before calling the wrapped function
def order(arg):
def wrapper(func):
print(f"order: {arg}")
return func
return wrapper
@order("one")
@order("two")
def inc(x):
return x + 1
print(inc(7))
#Output:
# order: two
# order: one
# 8
With functools.wraps the code would be
from functools import wraps
def order(arg):
def outer_wrapper(func):
@wraps(func)
def inner_wrapper(*args, **kwargs):
print(f"order: {arg}")
return func(*args, **kwargs)
return inner_wrapper
return outer_wrapper
@order("one")
@order("two")
def inc(x):
return x + 1
print(inc(7))
#Output:
# order: one
# order: two
# 8
Subtle difference but the order of side-effects (in this case the print statement) is reversed and unless the engineer using the decorator digs into the implementation detail of the decorator, there is no way of knowing how it will show up.
Is there a way for the decorator's implementor can avoid this ambiguity?