Skip to content

Decorators

Foreword

Notes and code snippets. Python 3. Consult the Hitchicker’s Guide to Python.


The Goal of Decorators

Decorators vs the Decorator Pattern

Python decorators are best equated to macros.

Definition

  • They modify functions, and in the case of class decorators, entire classes. They provide a simpler alternative to metaclasses.
  • A decorator itself is a callable that returns a callable.
  • A decorator is a function that takes a function object as its argument, and returns a function object, and in the process, makes necessary modifications to the input function, possibly enhancing it.

Useful for:

  • bookkeeping,
  • repeating insularity functionalities,
  • adding functionality of the function,
  • modifying the behavior of the function,
  • in Django, Flask or other web frameworks.

First Thing First

First dive

Decorators are easy to add or remove. They are nested functions; inserted in another function.

Below, inner() can live inside outer(). When you call outer(), you can also call inner().

from functools import wraps

def outer():
    number = 5

    def inner():
        print(number)

    inner()

outer() # print 5
inner() # cannot be called

Functions are first-class objects that can be passed around:

def apply(func, x, y):
    return func(x, y)

def add(x, y):
    return x + y

def sub(x, y):
    return x - y

print(apply(add, 5,5)) # call apply(), that calls add()
print(apply(sub, 2,8)) # call apply(), that calls sub()

Output:

10
-6

‘Predefine scope’: define the environment for the function. inner() has only access to outer() and number = 5.

def close():
    x = 5

    def inner():
        print(x)

    return inner

closure = close() # change the function name
closure() # call the 'new' function

Output:

5
def add_to_five(num):

    def inner():
        print(num + 5)

    return inner

fifteen = add_to_five(10)
fifteen()

Output:

15

Take two !

A decorator is a function that accept function as an argument and returns a function.

f() is an object, and it’s not different from classes (MyClass) or variables (a).

>>> a = 10
>>> def f():
...     pass
...
>>> class MyClass():
...     pass
...
>>> print dir()
['MyClass', '__builtins__', '__doc__', '__name__', '__package__', 'a', 'f']

Assign a function to a variable:

def func():
   print "func()"

funcObj = func
funcObj() # inheritance from func()

Functions can be passed around in the same way other types of object such as strings, integers, lists, etc.

A function can accept a function as an argument and return a new function object:

def myFunction(in_function):
   def out_function():
      pass
   return out_function

The myFunction is indeed a decorator because, by definition, a decorator is a function that takes a function object as its argument, and returns a function object (!!!).

Elaborate:

def myFunction(in_function):
   def out_function():
      print "Entry: ", in_function.__name__
      in_function()
      print "Exit: ", in_function.__name__
   return out_function

Invoking a Decorator

Put a simple_function into the decorator (myFunction) as an argument, and get a enhanced_function as a return value from the decorator.

def simple_function():
   pass

enhanced_function = myFunction(simple_function)

Apply the decorator syntax to the code above:

@myFunction
def simple_function():
   pass

@myFunction is a decorator line or an annotation line. The @ indicates the application of the decorator. A decorator is the function itself which takes a function, and returns a new function: myFunction.

When the compiler passes over this code, simple_function() is compiled. The resulting function object is passed to the myFunction code. It produces a function-like object that is substituted for the original simple_function().

The static method:

>>> class A:
...    def s(x):
...       print(x)
...    s = staticmethod(s)
... 
>>> A.s(10)
10

The equivalent code using a decorator looks like this:

>>> class A:
...    @staticmethod
...    def s(x):
...       print(x)
...
>>> A.s(10)
10

For example, suppose you’d like to do something at the entry and exit points of a function (perform some kind of security, tracing, locking, etc.):

@entryExit
def func1():
    print "inside func1()"

@entryExit
def func2():
    print "inside func2()"

Another example:

>>> def wrapper(f):
...    return f
...
>>> def foo():
...    pass
...

Then, the wrapper can be used for rebinding foo() like this:

>>> foo = wrapper(foo)

So, it’s a decorator:

>>> @wrapper
... def foo():
...    pass

With a decorator defined as below:

def decorator(f):
   #process function
   return f

Maps the following:

@decorator
def f(arg):
   return arg*arg

f(123)  # output 15129

Into:

def f(arg):
   print arg*arg
f = decorator(f)

Decoration maps the following line:

f(123)

Into:

decorator(f)(123)

A function decorator is applied to a function definition by placing it on the line before that function definition begins:

@myDecorator
def aFunction():
    print "inside aFunction"

The compiler passes over the code. The aFunction() is compiled. The resulting function object is passed to the myDecorator code. It produces a function-like object that is then substituted for the original aFunction().

Using Decorators

What should the decorator do? Anything!

Decorators allow you to modify code in functions or classes.

The only constraint upon the object returned by the decorator is that it can be used as a function. Any classes we use as decorators must implement __call__.

Expect the original function code to be used at some point:

class myDecorator(object):

    def __init__(self, f):
        print "inside myDecorator.__init__()"
        f() # Prove that function definition has completed

    def __call__(self):
        print "inside myDecorator.__call__()"

@myDecorator
def aFunction():
    print "inside aFunction()"

print "Finished decorating aFunction()"

aFunction()

Run this code:

inside myDecorator.__init__()
inside aFunction()
Finished decorating aFunction()
inside myDecorator.__call__()

The constructor for myDecorator is executed at the point of decoration of the function.

Call f() inside __init__(). The creation of f() is complete before the decorator is called.

The decorator constructor receives the function object being decorated.

Capture the function object in the constructor and later use it in the __call__() method.

When aFunction() is called after it has been decorated, the myDecorator.__call__() method is called instead of the original code. The act of decoration replaces the original function object.

Before decorators were added:

def foo():
    pass
foo = staticmethod(foo)

With the addition of the @ decoration operator:

@staticmethod
def foo():
    pass

This syntax brings the idea of “applying code to other code” (i.e.: macros).

Slightly More Useful

Use the code in the decorated functions:

class entryExit(object):

    def __init__(self, f):
        self.f = f

    def __call__(self):
        print "Entering", self.f.__name__
        self.f()
        print "Exited", self.f.__name__

@entryExit
def func1():
    print "inside func1()"

@entryExit
def func2():
    print "inside func2()"

func1()
func2()

Output:

Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2

The decorated functions now have the ‘Entering’ and ‘Exited’ trace statements around the call.

The constructor stores the argument, which is the function object. In the call, use the __name__ attribute of the function to display that function’s name.

Then call the function itself.

Using functions as decorators

Replace the original function with an object of a class that has a __call__() method. But a function object is also callable. From the previous example, use a function instead of a class:

def entryExit(f):
    def new_f():
        print "Entering", f.__name__
        f()
        print "Exited", f.__name__
    return new_f

@entryExit
def func1():
    print "inside func1()"

@entryExit
def func2():
    print "inside func2()"

func1()
func2()
print func1.__name__

new_f() is defined within the body of entryExit(). It is created and returned when entryExit() is called.

new_f() is a closure; it captures the actual value of f.

Once new_f() has been defined, it is returned from entryExit(). The decorator mechanism can assign the result as the decorated function.

The output of print func1.__name__ is new_f, because the new_f function has been substituted for the original function during decoration. If this is a problem, change the name of the decorator function before you return it:

def entryExit(f):
    def new_f():
        print "Entering", f.__name__
        f()
        print "Exited", f.__name__
    new_f.__name__ = f.__name__
    return new_f

Cases

1 - Adding $ to the return value from price() function

def dollar(fn):
    def new(*args):
        return '$' + str(fn(*args))
    return new

@dollar
def price(amount, tax_rate):
    return amount + amount*tax_rate

print price(100,0.1)

Output:

$110

The dollar decorator function takes the price() function, and returns enhanced the output from the original price() after modifying the inner working. Note that the decorator enables us to do it without making any changes on the price() function itself.

A decorator works as a wrapper, modifying the behavior of the code before and after a target function execution, without the need to modify the function itself, enhancing the original functionality.

With the pound or euro as well:

def pound(fn):
    def new(*args):
        return (u"\u00A3").encode('utf-8') + str(fn(*args))
        return '$' + str(fn(*args))
    return new

@pound
def price(amount, tax_rate):
    return amount + amount*tax_rate

print price(100,0.1)

2 - How many times a function called?

def count(f):
    def inner(*args, **kargs):
        inner.counter += 1
        return f(*args, **kargs)
    inner.counter = 0
    return inner

@count
def my_fnc():
    pass

if __name__ == '__main__':
    my_fnc()
    my_fnc()
    my_fnc()

    print 'my_fnc.counter=',my_fnc.counter

Output:

my_fnc.counter= 3

3 - Timer

import time
def timer(f):
    def inner(*args, **kargs):
        t = time.time()
        ret = f(*args, **kargs)
        print 'timer = %s' %(time.time()-t) 
        return ret
    return inner

@timer
def my_fnc():
    pass

if __name__ == '__main__':
    my_fnc()

Output:

timer = 5.96046447754e-06

More Cases and Examples

learnpython.org (tutorial, snippets)

Collected examples

def logme(func):
    import logging
    logging.basicConfig(level = logging.DEBUG)

    def inner():
        logging.debug("Called {}".format(func.__name__))

        return func()

    return inner
def logme(func):
    import logging
    logging.basicConfig(level = logging.DEBUG)

    def inner(*args, **kwargs): # * for tuple, ** for dict.
        logging.debug("Called {} with args {} and kwargs {}".format(
            func.__name__, args, kwargs)) # to print the tuple and dict.

        return func(*args, **kwargs) # to use the tuple and dict.

    return inner
def logme(func):
    import logging
    logging.basicConfig(level = logging.DEBUG)

    def inner(*args, **kwargs): # * for tuple, ** for dict.
        logging.debug("Called {} with args {} and kwargs {}".format(
            func.__name__, args, kwargs)) # to print the tuple and dict.

        return func(*args, **kwargs) # to use the tuple and dict.

    inner.__doc__ = func.__doc__
    inner.__name__ = func.__name__

    return inner
def logme(func):
    import logging
    logging.basicConfig(level = logging.DEBUG)

    @wraps(func) # decorator
    def inner(*args, **kwargs): # * for tuple, ** for dict.
        logging.debug("Called {} with args {} and kwargs {}".format(
            func.__name__, args, kwargs)) # to print the tuple and dict.

        return func(*args, **kwargs) # to use the tuple and dict.

    # replace all this
    #inner.__doc__ = func.__doc__
    #inner.__name__ = func.__name__
    # with  from functools import wraps  at the top
    # functools packages
    # wraps is a decorator; see above

    return inner
@logme
def sub(x, y):
    """Returns the difference between two numbers"""
    return x - y