Reputation: 1113
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?
Upvotes: 2
Views: 244
Reputation: 104712
It might help you understand what is going on if you added some additional print
statements in your code. Your two versions of the decorator are currently printing out at very different times, but that's obscured a bit because of the limited number of print statements. With a few more, you'll understand things better. Here's an updated version of the second version of your code.
def order(arg):
print(f"order: {arg}")
def outer_wrapper(func):
print(f"outer_wrapper: {arg}")
@wraps(func)
def inner_wrapper(*args, **kwargs):
print(f"inner_wrapper: {arg}")
result = func(*args, **kwargs)
print(f"inner_wrapper, after function call: {arg}")
return result
return inner_wrapper
return outer_wrapper
@order("one")
@order("two")
def inc(x):
return x + 1
print("after function definition")
print(inc(7))
The output will be:
order: one
order: two
outer_wrapper: two
outer_wrapper: one
after function definition
inner_wrapper: one
inner_wrapper: two
inner_wrapper, after function call: two
inner_wrapper, after function call: one
8
The first version of your code only prints out at the same time this code prints the outer_wrapper
messages, and the second version of your code only prints out the inner_wrapper
messages (the ones before the function call, not the ones after). Note that some of the messages print out when the function is defined (and the decorators are applied) and others print out later, when the function is called (and will print again if you call the function additional times).
Upvotes: 1