To become a professional Pythonista, you need to know what are Python decorators.
Python’s decorators provide a readable for extending the behavior of a function, method, or class. Using decorators is useful when you want to share a characteristic between multiple functions.
Decorating a function in Python follows this syntax:
@guard_zero def divide(x, y): return x / y
guard_zero updates the behavior of
divide() to check that
y is not
0 to prevent divisions by 0.
How to Use Decorators in Python
The best way to demonstrate decorators is by creating one and using it.
Let’s first create a function that divides two numbers:
def divide(x, y): return x / y
The issue with this function is it allows divisions by 0. You could solve this issue with an
if statement. But there is another option called decorators. Using a decorator, you don’t change the implementation of the function, but extend it from outside.
Let’s start by creating the
guard_zero decorator function. This function takes a function as an argument and creates an extended version of it:
def guard_zero(operate): def inner(x, y): if y == 0: print("Cannot divide by 0.") return return operate(x, y) return inner
operateis the function you want to extend.
innerfunction will be the extended version of the
operatefunction. It simply checks if the second input argument is zero before it runs the
- Finally, the
innerfunction is returned. It is the extended version of the original function passed as an argument.
You can now update the behavior of your
divide function by passing it into the
guard_zero decorator function:
divide = guard_zero(divide)
But there is a more Pythonic way to do this. You can use
guard_zero as a decorator in front of the function you want to extend:
@guard_zero def divide(x, y): return x / y
This looks syntactically better. Also, the intent is more clear this way.
Now you can test the
divide function with different inputs to see the extended behavior:
print(divide(5, 0)) print(divide(5, 2))
Cannot divide by 0. None 2.5
(There is a
None in the output because
This concludes the first example.
Here is all the code for your convenience:
def guard_zero(operate): def inner(x, y): if y == 0: print("Cannot divide by 0.") return return operate(x, y) return inner @guard_zero def divide(x, y): return x / y print(divide(5, 0)) # prints "Cannot divide by 0"
Now you know how to use a decorator to extend the functionality of a function. But when is this actually useful?
When to Use Decorators in Python
Why all the hassle with a decorator? In the previous example, you could have created an if-check and saved around 10 lines of code.
Yes, the decorator in the previous example was overkill. But the real power of decorators becomes clear when you can avoid repetition and improve code quality with them.
Imagine you have a bunch of similar functions:
def checkUsername(name): if type(name) is str: print("Correct format.") else: print("Incorrect format.") print("Handling username completed.") def checkName(name): if type(name) is str: print("Correct format.") else: print("Incorrect format.") print("Handling name completed.") def checkLastName(name): if type(name) is str: print("Correct format.") else: print("Incorrect format.") print("Handling last name completed.")
These functions all have the same if-else statement for validating the input. But this is unnecessary repetition of code.
Let’s improve the code by implementing a decorator that takes care of validating the input:
def string_guard(operate): def inner(name): if type(name) is str: print("Correct format.") else: print("Incorrect format.") operate(name) return inner
This decorator takes a function as an argument and extends the behavior to check if the input is a string.
Now, instead of repeating the same if-else over and over again, you can do this:
@string_guard def checkUsername(name): print("Handling username completed.") @string_guard def checkName(name): print("Handling name completed.") @string_guard def checkLastName(name): print("Handling last name completed.")
This is way better than the if-else mess seen earlier. Now the code is more readable and concise. Better yet, if you need more similar functions in the future, you can use the
string_guard on those too.
Now you know how decorators can help you write cleaner code and reduce unwanted repetition.
Next, let’s take a look at some useful built-in decorators you need to know in Python.
@Property Decorator in Python
Decorating a method with
@property makes it possible to access a method like an attribute:
weight.pounds() ---> weight.pounds
Let’s see how it works and when it is useful.
Let’s create a
Mass class that stores mass in kilos and pounds:
class Mass: def __init__(self, kilos): self.kilos = kilos self.pounds = kilos * 2.205
You can use this class as follows:
mass = Mass(1000) print(mass.kilos) print(mass.pounds)
Now, let’s modify the number of
kilos, and see what happens to
mass.kilos = 1200 print(mass.pounds)
Changing the number of
kilos did not affect the number of
pounds. This happens because you did not update the
This is not what you want. It would be better if the
pounds property was updated at the same.
To fix this problem, you can replace the
pounds attribute with a
pounds() method instead. When called, this method computes the
pounds on-demand based on the number of
class Mass: def __init__(self, kilos): self.kilos = kilos def pounds(self): return self.kilos * 2.205
Now you can test it:
mass = Mass(100) print(mass.pounds()) mass.kilos = 500 print(mass.pounds())
This works like a charm.
But obviously, calling mass.pounds does not work anymore. You need to call mass.pounds() instead. Thus, if you have
mass.pounds anywhere in your code, the program crashes.
You could go through your whole project and add the parenthesis for each
mass.pounds call. But there is a better alternative. Use the
@property decorator to extend your
pounds() method. This extends the
pounds() method such that it can be accessed like a property:
class Mass: def __init__(self, kilos): self.kilos = kilos @property def pounds(self): return self.kilos * 2.205
mass = Mass(100) print(mass.pounds) mass.kilos = 500 print(mass.pounds)
Now you don’t have to replace every
mass.pounds(). This saves you time and keeps the code outside the
Mass class unchanged.
By the way, as of this change, the
pounds() method is now called a getter method. It’s accessed like a property even though it’s a method.
@Classmethod Decorator in Python
A class method is useful when you need a method that involves the class but is not instance-specific.
A common use case for class methods is a “second initializer”.
To create a class method in Python, decorate a method inside a class with
Class Method as a Second Initializer in Python
Let’s say you have a
class Weight: def __init__(self, kilos): self.kilos = kilos
You create Weight instances like this:
w = Weight(100)
But what if you wanted to create a weight from pounds instead of kilos? In this case, you need to convert the number of kilos to pounds beforehand:
pounds = 220.5 kilos = pounds / 2.205 w2 = Weight(kilos)
But this is bad practice.
What if you could create a
Weight object from pounds by calling
To do this, you need to create a second initializer for the
Weight class. This is possible by using the
class Weight: def __init__(self, kilos): self.kilos = kilos @classmethod def from_pounds(cls, pounds): kilos = pounds / 2.205 return cls(kilos)
Let’s inspect the above code to understand how it works:
- The @classmethod marks the from_pounds() a class method. In this case it becomes the “second initializer”.
- The first argument
clsis a mandatory argument of a class method. It’s similar to
self. The difference is that
clsrepresents the whole class, not just an instance of it.
- Inside the
poundsare converted to
- Then the last line returns a new Weight object. (
Now it is possible to create a Weight object directly from pounds:
w = Weight.from_pounds(220.5) print(w.kilos)
@Staticmethod Decorator in Python
A static method is a method tied to a class, not to an instance of it. A static method could be a separate function. But if it closely relates to a class, it may be a good idea to place it inside of the class.
A static method does not take reference argument
self. It cannot access or modify the attributes of a class. It’s an object-independent method that works the same way for each instance of the class.
To create a static method, you need to decorate a method with the
For example, let’s add a static method
conversion_info into the
class Weight: def __init__(self, kilos): self.kilos = kilos @classmethod def from_pounds(cls, pounds): kilos = pounds / 2.205 return cls(kilos) @staticmethod def conversion_info(): print("Kilos are converted to pounds by multiplying by 2.205.")
To call this method, you can call it on the
Weight class directly.
Kilos are converted to pounds by multiplying by 2.205.
(You could also call this method on a
Weight instance to produce the same output.)
In Python, you can use decorators to extend the functionality of a function, method.
For example, you can implement a
guard_zero decorator to prevent dividing by 0. Then you can extend a function with it:
@guard_zero def divide(x, y): return x / y
Decorators are useful when you can avoid repetition and improve code quality.
There are useful built-in decorators in Python such as
@staticmethod. These decorators help you make your classes more elegant.
Thanks for reading. I hope you enjoyed it.