Well, evaluation of decorators does start at the last, but that is during definition time, not execution time. If you have the following code:
def decor_1(func):
print("Decor 1")
def wrap_1(*args, **kwargs):
print("Wrap 1")
return func(*args, **kwargs)
return wrap_1
def decor_2(func):
print("Decor 2")
def wrap_2(*args, **kwargs):
print("Wrap 2")
return func(*args, **kwargs)
return wrap_2
@decor_1
@decor_2
def f():
pass
print("== start ==")
f()
print("== end ==")
The result is:
Decor 2
Decor 1
== start ==
Wrap 1
Wrap 2
== end ==
This is because the def f
part is equivelent to decor_1(decor_2(f))
, but the resulting wrapped function is equivelent to:
def wrap_1(*args, **kwargs):
print("Wrap 1")
return wrap_2(*args, **kwargs)
Which is equivelent to:
def wrap_2(*args, **kwargs):
print("Wrap 1")
print("Wrap 2")
return f(*args, **kwargs)
Which is equivelent to:
def f(*args, **kwargs):
print("Wrap 1")
print("Wrap 2")
pass
So, when a function with normal decorators is called, the first wrapper executes first. However, if you execute the following mocking code:
import mock
def patch_1(*args, **kwargs):
print("Patch 1")
return mock.MagicMock(*args, **kwargs)
def patch_2(*args, **kwargs):
print("Patch 2")
return mock.MagicMock(*args, **kwargs)
@mock.patch("sys.argv", new_callable=patch_1)
@mock.patch("sys.argv", new_callable=patch_2)
def f(*args):
pass
print("== start ==")
f()
print("== end ==")
The result is:
== start ==
Patch 2
Patch 1
== end ==
Which is the opposite way round than expected (the execution order is reversed). Furthermore, the following code:
import mock
def patch_1(*args, **kwargs):
print("Patch 1")
return mock.MagicMock(*args, **kwargs)
def patch_2(*args, **kwargs):
print("Patch 2")
return mock.MagicMock(*args, **kwargs)
def nothing(func):
def nothing_wrap(*args, **kwargs):
print("Nothing")
return func(*args, **kwargs)
return nothing_wrap
@mock.patch("sys.argv", new_callable=patch_1)
@nothing
@mock.patch("sys.argv", new_callable=patch_2)
def f(*args):
pass
print("== start ==")
f()
print("== end ==")
Produces:
== start ==
Patch 1
Nothing
Patch 2
== end ==
Which is the expected order, even though @nothing
does nothing except print Nothing
. The reason is that during function definition, if the last decorator was a patch
decorator, mock
appends the patch to a list, instead of using the normal decorator method. This means that for the example before last, the list is [patch_2, patch_1]
, because the last decorator is executed first at definition time. When the fuction is called, mock
interates though that list front to back, so first patch_2
, then patch_1
is called. However, when I break it up using nothing
, the patches form two lists, [patch_1]
for the first patch
decorator, and [patch_2]
for the second patch decorator. As when the function is called, the first wrapper executes first, patch_1
, then patch_2
is called, the opposite order to before, and the same order that normal decorators execute in.