Basic custom decorator functions

Python decorators are functions which you call on top of another function to expand its functionality. Some common use cases include logging, adding delays or timeouts to function executions, extending behaviours and more. Here’s a basic example:

def decorator(func):
    def new_function():
        print("Extra Functionality")
        func()
    return new_function

@decorator
def initial_function():
    print("Initial Functionality")

initial_function()

In the above example, our initial function is called with decorator called ‘decorator’ just above it. Decorators are called using the ‘@’ sign. When this code is run, the terminal outputs:

Extra Functionality
Initial Functionality

Advanced custom decorator functions

The following example demonstrates creating decorator functions that accept parameters:

import time
class TimeOutError(Exception):
    pass
def delay(duration):
    def decorator(function):
        def wrapper(*args, **kwargs):
            time.sleep(duration)
            return function(*args, **kwargs)
        return wrapper
    return decorator
if __name__ == "__main__":
    @delay(5)
    def myFunction(inputText):
        return inputText
    
    print(myFunction("Hello, world!"))

In the above example, we’re creating a decorator function called ‘delay’. This function accepts a parameter of ‘duration’, which is an integer representing several seconds to wait before executing the decorated function.

When the above code is run, the program sleeps for 5 seconds, and then “Hello, world!” is printed.

Property decorators

Property decorators are built into Python and allow setter/getter functionality. This means you can create methods for a class that validate the attributes of the class. See the following example:

class House:
    def __init__(self, price):
        self._price = price
        
    @property
    def price(self):
        return self._price
    @price.setter
    def price(self, new_price):
        if new_price > 0 and isinstance(new_price, float):
            self._price = new_price
        else:
            print("Please enter a valid price")
    @price.deleter
    def price(self):
        self._price = 0
house = House(295000.00)
print(house.price)
house.price = 290000
del house.price
print(house.price)

In the above example, we have a class called ‘House’. When an instance of ‘House’ is created, it requires only a price to be provided. If you look at the methods of the class, we have 3 methods all called ‘price’, all with a different decorator. The 3 decorators: @property, @price.setter, and @price.deleter, are all automatically called when the instance attribute of ‘price’ is altered in the matching way.

To give context to the above, here is the output of the given code. Note which methods we’re running to understand the output:

295000.0
Please enter a valid price
0

Hopefully, this gives you a good introductory understanding of decorators in Python. These code snippets above are very helpful as a template when writing your own, so please go ahead and use them in your projects to help you get started with ease.