Python Decorators - Intro

Thomas
2 min readDec 9, 2020

Decorators in Python may appear confusing at first, but after you start implementing them, they’re very intuitive.

To put it simply, decorators allow us to modify the behaviour of a function without changing the code.

The prerequisite to grasping decorators is to understand the mechanics behind functions. As you may know functions can return values based on inputted arguments. A basic example shown here. The function is called with 10 being the argument (10*10 = 100).

def tentimes(number):
print(number * 10)

tentimes(10)
--------------------------------------------------------------------100

Functions can be manipulated and used as arguments in higher order functions. A function within a function. They are deemed ‘first-class’ objects.

We can think of these inner functions as the ‘children’ to the parent function (outer). The example below calls the inner function (indented) and then the parent function.

def outer():
print("Parent")

def inner():
print("Child")

inner() <-- Ensure this is indented
outer()
--------------------------------------------------------------------Parent
Child

The inner function is ‘local’ and can only be called (executed) from inside the parent.

We will now extend from this and include a parent argument (‘number’) with a conditional (if/else statement). The parent is then placed inside objects with a number and then called below.

def parent(number):

def child1():
print('Brian')
def child2():
print('Carol')

if number > 10:
return child1
else:
return child2

first = parent(31)
second = parent(3)

first()
second()
--------------------------------------------------------------------Brian
Carol

This is where we can transition to the decorator. These are expressed using the ‘@’ symbol and in the example below we have placed this on top of the ‘say_hello’ function. This will now inherit the parent and require us to simply call the function.

def parent(phase):
def child():
print('Start')
phase()
print('End')
return child

@parent <-- DECORATOR
def say_hello():
print('Middle')

say_hello()
--------------------------------------------------------------------Start
Middle
End

The ‘say_hello’ function essentially becomes the ‘child’. But what if we wish to then include an argument in the ‘say_hello’ function?

This will throw the error of :

‘child() takes 0 positional arguments but 1 was given’

We could solve this by adding an argument into ‘child’ but if we have multiple functions, some of which have arguments and some of which don’t, we run into complications.

The solution is to use *args,**kwargs inside the child function. This allows us to use any number of keyword arguments. We’ll use this in place as an argument for ‘child’ and ‘phase’ (as shown below)

def parent(phase):
def child(*args, **kwargs): <-- Solution
print('Start')
phase(*args, **kwargs)
print('End')
return child

@parent
def say_hello(X): <-- Argument (X)
print(X)

@parent
def say_bye(): <-- No Argument Given
print('Bye')

say_hello('Hello')
say_bye()
--------------------------------------------------------------------Start
Hello
End
Start
Bye
End

An example of where decorators can be especially useful is in web-frameworks such as Django. You can permit certain users (i.e. admin vs. customer) to access certain pages or only allow access if an individual is logged in.

To summarise, in order to initialise a decorator one would typically utilise a wrapper function inside the parent function. We then use ‘*args’ & ‘*kwargs’ as arguments in order for the decorated function to work efficiently.

--

--