Decorator & Property

Decorators

To better understand Decorators, consider a scenario of painting a house. If the house is already painted in one color let’s say white, we also have the option of adding another color of paint to a specific part of the house, without having to remove the entire paint of the house.

Likewise, Decorators give us the ability to add extra functionalities or features to our function without having to modify the original function. The original function which we do not want to modify is called the ‘Wrapper Function’ and the function which we use to add more functionalities to our original function is called ‘Decorator Function’.

We cannot call a decorator function directly instead we must use a closure.

Consider an example:

# Our decorator function takes another function as an argument
def decorator_function(function_name):
    # Our wrapper function
    def wrapper_function():
        print("From Wrapper Function")
        # Calls the required function with the parameter name
        function_name()
    # Return the inner function without calling it
    return wrapper_function

def custom_function():
    print(f"Working with Custom Function")

custom_function()
print("")


# Creating a closure
decorator_object = decorator_function(custom_function)
decorator_object()

The output we get:

First, we called the function to see what we would get as the output without a Decorator. We can see that it only called its print statement. However, when we used a closure to call our Decorator, we that an additional print statement from the wrapper class was called before our wrapped function.

We first create an object of the decorator function called “decorator_object” with the original function called “custom_function” given as an argument to it. The decorator function takes the name of a function as an argument, then the wrapper function provides the additional feature, which in this case is to print a sentence and then call the original function.

Python also provides a way to automatically call our decorator by creating a closure without having us manually create one. We can achieve this using the ‘@’ symbol followed by the name of the decorator. Consider how we can do this using our previous example:

# Our decorator function takes another function as an argument
def decorator_function(function_name):
    # Our wrapper function
    def wrapper_function():
        print("From Wrapper Function")
        # Calls the required function with the parameter name
        function_name()
    # Return it without calling the function
    return wrapper_function

# Calling our decorator function
@decorator_function
def custom_function():
    print(f"Working with Custom Function")

# Now we just have to call our function
custom_function()

This gives us the same output as when we used a closure to call our decorator function. Thus using ‘@’ eliminates the need to manually create a closure.

Property Decorator

Let’s say we created a website for a community of gamers and this takes in a couple of details as information. We use a class to store the information of different users, and within the constructor, we create a variable that uses the information provided to assign them a role in that community.

Suppose a user entered the wrong information and wanted to change it, we can achieve this by updating the object but it won’t be the preferred result. Consider the code for such a scenario:

class Gamer:
    def __init__(self, device, genre):
        self.gamer_device = device
        self.game_genre = genre
        self.gamer_role = self.gamer_device + " Advisor"

    def update_bio(self):
        return "I am a " + self.gamer_device + " gamer who plays " + self.game_genre + " games."

# Creating Object
gamer1 = Gamer(device="PC", genre="Strategy")

print("gamer1 Role is:", gamer1.gamer_role)
print("Your bio is:", gamer1.update_bio())

# Updating the gamer object
gamer1.gamer_device = "PlayStation"

print("\ngamer1 Role is:", gamer1.gamer_role)
print("Your bio is:", gamer1.update_bio())

When we run this, we see:

We see that even though we have updated the gamer’s device variable, that change is not reflected in the gamer’s role variable. This is because the value was initialized when the object’s constructor was called. The first solution to this problem would be to create a simple function instead of creating it in the constructor. A function such as:

def gamer_role(self):
    return self.gamer_device + " Advisor"

Now to view the change, we would simply have to call the object’s function as:

print("gamer1 Role is:", gamer1.gamer_role())
print("Your bio is:", gamer1.update_bio())

When we run this, we get:

While this might work as a solution, there is a notable difference. The gamer’s role is no longer an attribute of the class but is now a function that we need to call. Now if the code has gone out to thousands of developers, they would now have to individually change each instance of that attribute to a function.

To make changes to a value initialized in the constructor and still retain it as an attribute we need to use the ‘Property Declarator’ (@property). The Property Declarator allows us to modify values we initialized in our constructor. Now let’s modify our class using the @property Decorator.

class Gamer:
    def __init__(self, device, genre):
        self.gamer_device = device
        self.game_genre = genre

    @property
    def gamer_role(self):
        return self.gamer_device + " Advisor"

    def update_bio(self):
        return "I am a " + self.gamer_device + " gamer who plays " + self.game_genre + " games."

# Creating Object
gamer1 = Gamer(device="PC", genre="Strategy")

print("gamer1 Role is:", gamer1.gamer_role)
print("Your bio is:", gamer1.update_bio())

# Updating the gamer object
gamer1.gamer_device = "PlayStation"

print("\ngamer1 Role is:", gamer1.gamer_role)
print("Your bio is:", gamer1.update_bio())

We get the same output as before:

However, the biggest difference is that we can now call a gamer’s role like it is an attribute instead of a function.

What have we learned?

  • What are Decorators?
  • What is a wrapper function?
  • What symbol simplifies the process of working with Decorators?
  • What is the Property Decorator?
  • What is a use case for @property?
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments