What Are Python Decorators

gray laptop computer turned-on

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

Here 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
  • The operate is the function you want to extend.
  • The inner function will be the extended version of the operate function. It simply checks if the second input argument is zero before it runs the operate function.
  • Finally, the inner function 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))

Output:

Cannot divide by 0.
None

2.5

(There is a None in the output because guard_zero returns None when y is 0.)

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.

Example

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)

Output:

1000
2205

Now, let’s modify the number of kilos, and see what happens to pounds:

mass.kilos = 1200
print(mass.pounds)

Output:

2205

Changing the number of kilos did not affect the number of pounds. This happens because you did not update the pounds separately.

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 kilos.

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())

Result:

220.5
1102.5

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 with 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 @classmethod.

Class Method as a Second Initializer in Python

Let’s say you have a Weight class:

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 weight.from_pounds(220.5) instead?

To do this, you need to create a second initializer for the Weight class. This is possible by using the @classmethod decorator:

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 cls is a mandatory argument of a class method. It’s similar to self. The difference is that cls represents the whole class, not just an instance of it.
  • Inside the from_pounds method, the pounds are converted to kilos first.
  • Then the last line returns a new Weight object. (cls(kilos) means Weight(kilos))

Now it is possible to create a Weight object directly from pounds:

w = Weight.from_pounds(220.5)
print(w.kilos)

Output:

100

@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 @staticmethod.

For example, let’s add a static method conversion_info into the Weight class:

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.

Weight.conversion_info()

Output:

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.)

Conclusion

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 @property, @classmethod, and @staticmethod. These decorators help you make your classes more elegant.

Thanks for reading. I hope you enjoyed it.

Happy decorating!

Further Reading

50 Python Interview Questions and Answers

50+ Buzzwords of Web Development

Share on facebook
Facebook
Share on google
Google+
Share on twitter
Twitter
Share on linkedin
LinkedIn
Share on pinterest
Pinterest

Leave a Comment

Your email address will not be published. Required fields are marked *