Python decorators are an incredibly useful construct in Python. Using decorators in Python, we can modify the behaviour of a function by wrapping it inside another function. Decorators enable us to write cleaner code and share functionality. This article is a tutorial on not only how to use decorators but how to create them.
Prerequisite knowledge
The topic of decorators in Python requires some background knowledge. Below, I have listed some concepts you should already be familiar with to make sense of this tutorial. I have also linked resources where you can brush up on the concepts if need be.
Basic Python
This topic is a more intermediate/advanced topic. As a result, before attempting to learn, you should already be familiar with the basics of Python, such as data types, functions, objects, and classes.
You should also understand some object-oriented concepts such as getters, setters, and constructors. If you are not familiar with the Python programming language, here are some resources to get you started.
Functions are first-class citizens
In addition to basic Python, you should also be aware of this more advanced concept in Python. Functions, and pretty much everything else in Python, are objects like int
or string
. Because they are objects, you can do a few things with them, namely:
- You can pass a function as an argument to another function in the same way you pass a
string
orint
as a function argument. - Functions can also be returned by other functions like you would return other
string
orint
values. - Functions can be stored in variables
In fact, the only difference between functional objects and other objects is functional objects contain the magic method __call__()
.
Hopefully, at this point, you are comfortable with the prerequisite knowledge. We can begin discussing the main topic.
What is a Python Decorator?
A Python decorator is simply a function that takes in a function as an argument and returns a modified version of the function that was passed in. In other words, the function foo is a decorator if it takes in, as an argument, the function bar
and returns another function baz
.
The function baz
is a modification of bar
in the sense that within the body of baz
, there is a call to the function bar
. However, before and after the call to bar
, baz
can do anything. That was a mouthful; here is some code to illustrate the situation:
# Foo is a decorator, it takes in another function, bar as an argument
def foo(bar):
# Here we create baz, a modified version of bar
# baz will call bar but can do anything before and after the function call
def baz():
# Before calling bar, we print something
print("Something")
# Then we run bar by making a function call
bar()
# Then we print something else after running bar
print("Something else")
# Lastly, foo returns baz, a modified version of bar
return baz
How to Create a Decorator in Python?
To illustrate how decorators are created and used in Python, I am going to illustrate this with a simple example. In this example, we will create a logger decorator function that will log the name of the function it is decorating every time that function runs.
To get started, we created the decorator function. The decorator takes in func
as an argument. func
is the function we are decorating.
def create_logger(func):
# The function body goes here
Inside the decorator function, we are going to create our modified function that will log the name of func
before running func
.
# Inside create_logger
def modified_func():
print("Calling: ", func.__name__)
func()
Next, the create_logger
function will return the modified function. As a result, our entire create_logger
function will look like this:
def create_logger(func):
def modified_func():
print("Calling: ", func.__name__)
func()
return modified_function
We are done creating the decorator. The create_logger
function is a simple example of a decorator function. It takes in func
, which is the function we are decorating, and returns another function, modified_func
. modified_func
first logs the name of func
, before running func
.
How to use decorators in Python
To use our decorator, we use the @
syntax like so:
@create_logger
def say_hello():
print("Hello, World!")
Now we can call say_hello() in our script, and the output should be the following text:
Calling: say_hello
"Hello, World"

But what is the @create_
logger doing? Well, it is applying the decorator to our say_hello function. To better understand what is doing, the code immediately below this paragraph would achieve the same result as putting @create_logger
before say_hello
.
def say_hello():
print("Hello, World!")
say_hello = create_logger(say_hello)
In other words, one way to use decorators in Python is to explicitly call the decorator passing in the function as we did in the code above. The other and more concise way is to use the @
syntax.
In this section, we covered how to create Python decorators.
Slightly more complicated examples
The above example was a simple case. There are slightly more complex examples such as when the function we are decorating takes in arguments. Another more complicated situation is when you want to decorate an entire class. I am going to cover both of these situations here.
When the function takes in arguments
When the function you are decorating takes in arguments, the modified function should receive the arguments and pass them when it eventually makes the call to the unmodified function. If that sounds confusing, let me explain in foo-bar terms.
Recall that foo
is the decorator function, bar
is the function we are decorating and baz
is the decorated bar
. In that case, bar will take in the arguments and pass them to baz
during the call to baz
. Here is a code example to solidify the concept:
def foo(bar):
def baz(*args, **kwargs):
# You can do something here
___
# Then we make the call to bar, passing in args and kwargs
bar(*args, **kwargs)
# You can also do something here
___
return baz
If the *args
and **kwargs
look unfamiliar; they are simply pointers to the positional and keyword arguments, respectively.
It is important to note that baz
has access to the arguments and can therefore perform some validation of the arguments before calling bar
.
An example would be if we had a decorator function, ensure_string
that would ensure that the argument passed to a function it is decorating is a string; we would implement it like so:
def ensure_string(func):
def decorated_func(text):
if type(text) is not str:
raise TypeError('argument to ' + func.__name__ + ' must be a string.')
else:
func(text)
return decorated_func
We could decorate the say_hello
function like so:
@ensure_string
def say_hello(name):
print('Hello', name)
Then we could test the code using this:
say_hello('John') # Should run just fine
say_hello(3) # Should throw an exception
And it should produce the following output:
Hello John
Traceback (most recent call last):
File "/home/anesu/Documents/python-tutorial/./decorators.py", line 20, in <module> say hello(3) # should throw an exception
File "/home/anesu/Documents/python-tu$ ./decorators.pytorial/./decorators.py", line 7, in decorated_func raise TypeError('argument to + func._name_ + must be a string.')
TypeError: argument to say hello must be a string. $0

As expected, the script managed to print ‘Hello John’ because ‘John’ is a string. It threw an exception when trying to print ‘Hello 3’ because ‘3’ wasn’t a string. The ensure_string
decorator could be used to validate the arguments of any function that requires a string.
Decorating a class
In addition to just decorating functions, we can also decorate classes. When you add a decorator to a class, the decorated method replaces the class’s constructor/initiator method(__init__).
Going back to foo-bar, suppose foo is our decorator and Bar is the class we are decorating, then foo will decorate Bar.__init__. This will be useful if we want to do anything before objects of the type Bar
are instantiated.
This means that the following code
def foo(func):
def new_func(*args, **kwargs):
print('Doing some stuff before instantiation')
func(*args, **kwargs)
return new_func
@foo
class Bar:
def __init__(self):
print("In initiator")
Is equivalent to
def foo(func):
def new_func(*args, **kwargs):
print('Doing some stuff before instantiation')
func(*args, **kwargs)
return new_func
class Bar:
def __init__(self):
print("In initiator")
Bar.__init__ = foo(Bar.__init__)
In fact, instantiating an object of class Bar, defined using either of the two methods, should give you the same output:
Doing some stuff before instantiation
In initiator

Example Decorators in Python
While you can define your own decorators, there are some that are already built into Python. Here are some of the common decorators you may encounter in Python:
@staticmethod
The static method is used on a class to indicate that the method it is decorating is a static method. Static methods are methods that can run without the need to instantiate the class. In the following code example, we create a class Dog
with a static method bark
.
class Dog:
@staticmethod
def bark():
print('Woof, woof!')
Now the bark
method can be accessed like so:
Dog.bark()
And running the code would produce the following output:
Woof, woof!

As I mentioned in the section on How to use Decorators, decorators can be used in two ways. The @
syntax being the more concise being one of the two. The other method is to call the decorator function, passing in the function we want to decorate as an argument. Meaning the code above achieves the same thing as the code below:
class Dog:
def bark():
print('Woof, woof!')
Dog.bark = staticmethod(Dog.bark)
And we can still use the bark
method in the same way
Dog.bark()
And it would produce the same output
Woof, woof!

As you can see, the first method is cleaner and it’s more obvious that the function is a static function before you have even started reading the code. As a result, for the remaining examples, I will use the first method. But just remember the second method is an alternative.
@classmethod
This decorator is used to indicate that the method it is decorating is a class method. Class methods are similar to static methods in that they both do not require that the class be instantiated before they can be called.
However, the main difference is that class methods have access to class attributes while static methods do not. This is because Python automatically passes the class as the first argument to a class method whenever it is called. To create a class method in Python, we can use the classmethod
decorator.
class Dog:
@classmethod
def what_are_you(cls):
print("I am a " + cls.__name__ + "!")
To run the code, we simply call the method without instantiating the class:
Dog.what_are_you()
And the output is:
I am a Dog!

@property
The property decorator is used to label a method as a property setter. Going back to our Dog example, let’s create a method that retrieves the name of the Dog.
class Dog:
# Creating a constructor method that takes in the dog's name
def __init__(self, name):
# Creating a private property name
# The double underscores make the attribute private
self.__name = name
@property
def name(self):
return self.__name
Now we can access the dog’s name like a normal property,
# Creating an instance of the class
foo = Dog('foo')
# Accessing the name property
print("The dog's name is:", foo.name)
And the result of running the code would be
The dog's name is: foo

@property.setter
The property.setter decorator is used to create a setter method for our properties. To use the @property.setter
decorator, you replace property
with the name of the property, you are creating a setter for. For example, if you are creating a setter for the method for the property foo, your decorator will be @foo.setter
. Here is a Dog example to illustrate:
class Dog:
# Creating a constructor method that takes in the dog's name
def __init__(self, name):
# Creating a private property name
# The double underscores make the attribute private
self.__name = name
@property
def name(self):
return self.__name
# Creating a setter for our name property
@name.setter
def name(self, new_name):
self.__name = new_name
To test the setter, we can use the following code:
# Creating a new dog
foo = Dog('foo')
# Changing the dog's name
foo.name = 'bar'
# Printing the dog's name to the screen
print("The dog's new name is:", foo.name)
Running the code will produce the following output:
The dogs's new name is: bar

Importance of decorators in Python
Now that we have covered what decorators are, and you have seen some examples of decorators, we can discuss why decorators are important in Python. Decorators are important for several reasons. Some of them, I have listed below:
- They enable code reusability: In the
logging
example given above, we could use the @create_logger on any function we want. This allows us to add logging functionality to all our functions without manually writing it for each function. - They allow you to write modular code: Again, going back to the logging example, with decorators, you can separate the core function, in this case
say_hello
from the other functionality you need, in this case, logging. - They enhance frameworks and libraries: Decorators are extensively used in Python frameworks and libraries to provide additional functionality. For example, in web frameworks like Flask or Django, decorators are used for defining routes, handling authentication, or applying middleware to specific views.
Final Words
Decorators are incredibly useful; you can use them to extend functions without altering their functionality. This is useful when you want to time the performance of functions, log whenever a function is called, validate arguments before calling a function, or verify permissions before a function is run. Once you understand decorators, you will be able to write code in a cleaner way.
Next, you may want to read our articles on tuples and using cURL in Python.