Skip to content

Instantly share code, notes, and snippets.

@matsjoyce
Created April 10, 2015 20:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matsjoyce/30695312d57a901b25cf to your computer and use it in GitHub Desktop.
Save matsjoyce/30695312d57a901b25cf to your computer and use it in GitHub Desktop.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment