©2018 Bloomberg Finance L.P.
All rights reserved.

A Taxonomy of Decorators: A-E

Andy Fundinger, Senior Engineer

EuroPython, Edinburgh, Scotland, July 29, 2018


  • Who I am
  • What is Bloomberg?
  • What is this talk?
    • we use decorators, but when we go to talk about them it's hard to get them into categories
    • with a common terminology we can discuss more easily
  • Who is this talk for?
    • Intermediate developers who can write decorators, but maybe aren't sure when and why
    • Architects who may need to work across teams to implement and manage their design

Decorator Syntax and Implementation

Any decorator can be replaced with code in the decorated functions. However, decorators allow this code to be reused and factored out of the functions.

  • Decorators without the @ sign
  • Function Decorators
  • Class Decorators
  • Decorators with arguments
  • Decorators written as classes

Generally decorators insert a section of code of arbitrary complexity in a single line. Debuggers generally skip over this code--for better or worse.

Basic Syntax

Decorators without the @ sign

The oldest decorators in Python are @staticmethod and @classmethod. Dating back to Python 2.2 we used those like this:

In [53]:
class Util(object):
    def wibble():
    wibble = staticmethod(wibble)

The @ sign

The @ sign was added in 2.4 as a syntactic sugar for this pattern

In [54]:
class Util(object):
    def wibble():
    def disabled():

We can write our own decorators as long as we accept a function and return a replacement function.

In [1]:
def null_it(func):
    'Replace with no-op'
    def null(*args,**kwargs): pass
    return null

Class Decorators

In Python 2.6 we can decorate classes too,

In [3]:
class DeadClass:

DeadClass() is None

Decorators with arguments

Decorators with arguments aren't actually doing anything all that special. They simply call the function with the arguments and that function returns the actual decorator.

In [70]:
def mult_arg(mult):
    def deco(func):
        def wrapper(arg_one, *args, **kwargs):
            return func(arg_one*mult, *args, **kwargs)
        return wrapper
    return deco

def print_x(x):
print_x('Hello ')
Hello Hello Hello 

Notice, this is two closures, one for the argument (mult) and one for the function (func).

Decorators written as classes

Normally we write decorators that are closures, but there's no particular benefit to this.

In [71]:
import random

class trace_it:
    def __init__(self, func):
        self.func = func
    def __call__(self, *args, **kwargs):
        print(args, kwargs)
        return self.func(*args, **kwargs)
def rand(min_val, max_val):
    return random.randint(min_val, max_val)
(10, 30) {}

A - Argument Changing Decorators

  • add or remove an argument when the function is called
  • change the value or type of an argument at call time
  • similarly alter the return value


  • calling the apparent signature does not actually work
  • calling a function for a test requires injecting data to drive the decorator properly

Example: pytest.mark.parametrize()

In [74]:
import pytest

@pytest.mark.parametrize("test_input,expected", [
    ("3+5", 8),
    ("2+4", 6),
    ("6*9", 42),

def test_eval(test_input, expected):
    assert eval(test_input) == expected

Example implementation -- adding the func name in the call

In [76]:
def fn_with_name(func):
    def wrapper(*args, **kwargs):
    return wrapper
def self_aware(name, oth):
    print('{name}: {oth}'.format(name=name, oth=oth))
self_aware: here

B - Binding Decorators

  • implement the Descriptor Protocol to change how functions behave
  • the standard library includes @staticmethod, @classmethod, and @property


  • creates an alternative to instance methods and attributes
  • new language patterns arguably better fitting other languages
  • time shifts otherwise normal exceptions or introduces new ones

Example: SQLAlchemy Hybrid Properties

In [78]:
#from sqlalchemy import func
hybrid_property= MagicMock()

class Interval(object):
    def radius(self):
        return abs(self.length) / 2

    def radius(cls):
        return func.abs(cls.length) / 2

Example implementation -- instance method

In [34]:
class instance_method:
    def __init__(self, func):
        self.func = func
    def __get__(self, inst, cls):
        if inst is None:
            raise TypeError(f'{self.func.__name__} is only valid on instances.')
        return self.func.__get__(inst, cls)
class GoodClass:
    def simple_method(self):
    def normal_method(self):
In [35]:
In [32]:
<function __main__.GoodClass.normal_method>
In [40]:
TypeError                                 Traceback (most recent call last)
<ipython-input-40-5ffb98a5765b> in <module>()
----> 1 GoodClass.simple_method

<ipython-input-34-53c5e8f52716> in __get__(self, inst, cls)
      4     def __get__(self, inst, cls):
      5         if inst is None:
----> 6             raise TypeError(f'{self.func.__name__} is only valid on instances.')
      7         return self.func.__get__(inst, cls)

TypeError: simple_method is only valid on instances.

C - Control Flow Decorators

  • change whether a function will be called and how many times


  • a predictable control flow now has a hidden conditional
  • a single invocation might now lead to 0, 1, or many executions of the function

Example: Retry Decorator

In [39]:
#from retrying import retry
retry = MagicMock()

def do_something_unreliable():
    if random.randint(0, 10) > 1:
        raise IOError("Broken sauce, everything is hosed!!!111one")
        return "Awesome sauce!"

<MagicMock name='mock()()' id='139994135494384'>

Example implementation -- infinite retry

In [9]:
def infinite_retry(func):
    def wrapper(*args, **kwargs):
        while True:
                return func(*args, **kwargs)
            except RuntimeError as e:
    return wrapper

def random_fail(max_value):
    ret = random.randint(-100, max_value)
    if ret<0:
        raise RuntimeError("Invalid negative number {ret}".format(ret=ret))
    return ret

Invalid negative number -79
Invalid negative number -67

D - Descriptive Decorators

  • add the decorated object to some sort of collection
  • this collection will serve some other purpose such as:
    • documentation
    • dispatching
    • plugins


  • it's unclear how dispatching will be done as a result of registration
  • similarly it's hard to see where the registration is maintained

Example pytest.marks

In [90]:
#import pytest
pytest = MagicMock()

def test_send_http():
    pass # perform some webtest test for your app
def test_something_quick():
def test_another():
class TestClass(object):
    def test_method(self):

Example: flask.app.route

In [92]:
#app = Flask(__name__)
app = MagicMock()

def hello_world():
    return 'Hello, World!'

Example implementation -- qa list

In [97]:
import warnings 

def qa(func):
    return func

def new_code(): pass

def refactored_code(): pass

def well_trusted_code(): pass
In [98]:
['new_code', 'refactored_code']

E - Execution Decorators

  • reads the method/class code
  • may reinterpret the source code to basically not be python


  • many
  • and more

This truly means that the code you wrote is changed--by the decorator--to some other code that is then executed. It might:

  • be analyzed for dependencies
  • have objects in the ast swapped out, injected or removed
  • be recompiled with different rules

Example: cython

In [101]:
cython = MagicMock()

@cython.locals(a=cython.double, b=cython.double, n=cython.p_double)
def foo(a, b, x, y):
    n = a*b

Example implementation -- code replacer

In [103]:
def replacer(old, new):
    def deco(func):
        source = inspect.getsource(func.__code__)
        lines = source.split('\n')
        new_source = lines[1]+'\n'+('\n'.join(lines[2:]).replace(old, new))
        return globals()[func.__name__]
    return deco
In [102]:
def sample(a, b):
    x = a + b
    y = x * 2
    print('Sample: ' + str(y))
Sample: 10
In [105]:
def sample(a, b):
    x = a + b
    y = x * 2
    print('Sample: ' + str(y))
Sample: 26
In [106]:
def sample(a, b):
    x = a + b
    y = x * 2
    print('Sample: ' + str(y))
Sa*3mple: 14

Other tools available for execution decorators

  • bytecode manipulation
  • ast manipulation


Our Taxonomy

  • Argument changing
    • @click.option
    • @flask.templated
    • @django.views.decorators.gzip.gzip_page
  • Binding
    • @variants.primary
    • @pyramid.decorator.reify
  • Control flow
    • @functools.lru_cache
    • @django.views.decorators.http.require_http_methods
    • @twisted.internet.defer.inlineCallbacks
  • Descriptive
    • @numpy.testing.decorators.setastest
  • Execution
    • @numba.jit
  • ?