Python Tutorial_ Easy Introduction into Decorators and Decoration
Python Tutorial_ Easy Introduction into Decorators and Decoration
Home Python 2 Tutorial Python 3 Tutorial Advanced Topics Numerical Programming Machine Learning Tkinter Tutorial Contact
Decorators
def f(x):
def g(y):
return y + x + 3
return g
nf1 = f(1)
nf2 = f(3)
print(nf1(1))
print(nf2(1))
5
7
We will implement a polynomial "factory" function now. We will start with writing a version which can create polynomials of degree 2.
The Python implementation as a polynomial factory function can be written like this:
p1 = polynomial_creator(2, 3, -1)
p2 = polynomial_creator(-1, 2, 1)
We can generalize our factory function so that it can work for polynomials of arbitrary degree:
def polynomial_creator(*coefficients):
""" coefficients are in the form a_0, a_1, ... a_n
"""
def polynomial(x):
res = 0
for index, coeff in enumerate(coefficients):
res += coeff * x** index
return res
return polynomial
p1 = polynomial_creator(4)
p2 = polynomial_creator(2, 4)
p3 = polynomial_creator(2, 3, -1, 8, 1)
p4 = polynomial_creator(-1, 2, 1)
If you want to learn more about polynomials and how to create a polynomial class, you can continue with our chapter on Polynomials.
A Simple Decorator
def our_decorator(func):
def function_wrapper(x):
print("Before calling " + func.__name__)
func(x)
print("After calling " + func.__name__)
return function_wrapper
def foo(x):
print("Hi, foo has been called with " + str(x))
If you look at the following output of the previous program, you can see what's going on. After the decoration "foo = our_decorator(foo)", foo is a reference to the 'function_wrapper'. 'foo' will be called inside of
'function_wrapper', but before and after the call some additional code will be executed, i.e. in our case two print functions.
The decoration in Python is usually not performed in the way we did it in our previous example, even though the notation foo = our_decorator(foo) is catchy and easy to grasp. This is the reason, why we used it! You can
also see a design problem in our previous approach. "foo" existed in the same program in two versions, before decoration and after decoration.
We will do a proper decoration now. The decoration occurrs in the line before the function header. The "@" is followed by the decorator function name.
We will rewrite now our initial example. Instead of writing the statement
foo = our_decorator(foo)
we can write
@our_decorator
But this line has to be directly positioned in front of the decorated function. The complete example looks like this now:
def our_decorator(func):
def function_wrapper(x):
print("Before calling " + func.__name__)
func(x)
print("After calling " + func.__name__)
return function_wrapper
@our_decorator
def foo(x):
print("Hi, foo has been called with " + str(x))
foo("Hi")
We can decorate every other function which takes one parameter with our decorator 'our_decorator'. We demonstrate this in the following. We have slightly changed our function wrapper, so that we can see the
result of the function calls:
def our_decorator(func):
def function_wrapper(x):
print("Before calling " + func.__name__)
res = func(x)
print(res)
print("After calling " + func.__name__)
return function_wrapper
@our_decorator
def succ(n):
return n + 1
succ(10)
It is also possible to decorate third party functions, e.g. functions we import from a module. We can't use the Python syntax with the "at" sign in this case:
def our_decorator(func):
def function_wrapper(x):
print("Before calling " + func.__name__)
res = func(x)
print(res)
print("After calling " + func.__name__)
return function_wrapper
sin = our_decorator(sin)
cos = our_decorator(cos)
Summarizing we can say that a decorator in Python is a callable Python object that is used to modify a function, method or class definition. The original object, the one which is going to be modified, is passed to a
decorator as an argument. The decorator returns a modified object, e.g. a modified function, which is bound to the name used in the definition.
The previous function_wrapper works only for functions with exactly one parameter. We provide a generalized version of the function_wrapper, which accepts functions with arbitrary parameters in the following
example:
def our_decorator(func):
def function_wrapper(*args, **kwargs):
print("Before calling " + func.__name__)
res = func(*args, **kwargs)
print(res)
print("After calling " + func.__name__)
return function_wrapper
random = our_decorator(random)
randint = our_decorator(randint)
choice = our_decorator(choice)
random()
randint(3, 8)
choice([4, 5, 6])
The following program uses a decorator function to ensure that the argument passed to the function factorial is a positive integer:
def argument_test_natural_number(f):
def helper(x):
if type(x) == int and x > 0:
return f(x)
else:
raise Exception("Argument is not an integer")
return helper
@argument_test_natural_number
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n-1)
for i in range(1,10):
print(i, factorial(i))
print(factorial(-1))
def call_counter(func):
def helper(x):
helper.calls += 1
return func(x)
helper.calls = 0
return helper
@call_counter
def succ(x):
return x + 1
print(succ.calls)
for i in range(10):
succ(i)
print(succ.calls)
0
10
We pointed out that we can use our previous decorator only for functions, which take exactly one parameter. We will use the *args and **kwargs notation to write decorators which can cope with functions with an
arbitrary number of positional and keyword parameters.
def call_counter(func):
def helper(*args, **kwargs):
helper.calls += 1
return func(*args, **kwargs)
helper.calls = 0
return helper
@call_counter
def succ(x):
return x + 1
@call_counter
def mul1(x, y=1):
return x*y + 1
print(succ.calls)
for i in range(10):
succ(i)
mul1(3, 4)
mul1(4)
mul1(y=3, x=2)
print(succ.calls)
print(mul1.calls)
0
10
3
def evening_greeting(func):
def function_wrapper(x):
print("Good evening, " + func.__name__ + " returns:")
func(x)
return function_wrapper
def morning_greeting(func):
def function_wrapper(x):
print("Good morning, " + func.__name__ + " returns:")
func(x)
return function_wrapper
@evening_greeting
def foo(x):
print(42)
foo("Hi")
These two decorators are nearly the same, except for the greeting. We want to add a parameter to the decorator to be capable of customizing the greeting, when we do the decoration. We have to wrap another
function around our previous decorator function to accomplish this. We can now easy say "Good Morning" in the Greek way:
def greeting(expr):
def greeting_decorator(func):
def function_wrapper(x):
print(expr + ", " + func.__name__ + " returns:")
func(x)
return function_wrapper
return greeting_decorator
@greeting("καλημερα")
def foo(x):
print(42)
foo("Hi")
The output:
If we don't want or cannot use the "at" decorator syntax, we can do it with function calls:
def greeting(expr):
def greeting_decorator(func):
def function_wrapper(x):
print(expr + ", " + func.__name__ + " returns:")
func(x)
return function_wrapper
return greeting_decorator
def foo(x):
print(42)
greeting2 = greeting("καλημερα")
foo = greeting2(foo)
foo("Hi")
Of course, we don't need the additional definition of "greeting2". We can directly apply the result of the call "greeting("καλημερα")" on "foo":
foo = greeting("καλημερα")(foo)
The way we have defined decorators so far hasn't taken into account that the attributes
def greeting(func):
def function_wrapper(x):
""" function_wrapper of greeting """
print("Hi, " + func.__name__ + " returns:")
return func(x)
return function_wrapper
@greeting
def f(x):
""" just some silly function """
return x + 4
f(10)
print("function name: " + f.__name__)
print("docstring: " + f.__doc__)
print("module name: " + f.__module__)
Hi, f returns:
function name: function_wrapper
docstring: function_wrapper of greeting
module name: greeting_decorator
We can save the original attributes of the function f, if we assign them inside of the decorator. We change our previous decorator accordingly and save it as greeting_decorator_manually.py:
def greeting(func):
def function_wrapper(x):
""" function_wrapper of greeting """
print("Hi, " + func.__name__ + " returns:")
return func(x)
function_wrapper.__name__ = func.__name__
function_wrapper.__doc__ = func.__doc__
function_wrapper.__module__ = func.__module__
return function_wrapper
Hi, f returns:
function name: f
docstring: just some silly function
module name: __main__
Fortunately, we don't have to add all this code to our decorators to have these results. We can import the decorator "wraps" from functools instead and decorate our function in the decorator with it:
def greeting(func):
@wraps(func)
def function_wrapper(x):
""" function_wrapper of greeting """
print("Hi, " + func.__name__ + " returns:")
return func(x)
return function_wrapper
class A:
def __init__(self):
print("An instance of A was initialized")
x = A()
print("now calling the instance:")
x(3, 4, x=11, y=10)
print("Let's call it again:")
x(3, 4, x=11, y=10)
We can write a class for the fibonacci function by using the __call__ method:
class Fibonacci:
def __init__(self):
self.cache = {}
fib = Fibonacci()
for i in range(15):
print(fib(i), end=", ")
The output:
You can find further information on the __class__ method in the chapter Magic Functions of our tutorial.
def decorator1(f):
def helper():
print("Decorating", f.__name__)
f()
return helper
@decorator1
def foo():
print("inside foo()")
foo()
class decorator2:
def __call__(self):
print("Decorating", self.f.__name__)
self.f()
@decorator2
def foo():
print("inside foo()")
foo()
Decorating foo
inside foo()
© 2011 - 2018, Bernd Klein, Bodenseo; Design by Denise Mitchinson adapted for python-course.eu by Bernd Klein