Python Design Patterns for better Development(Decorator Pattern : 1)

Python Decorator patterns for clean and maintainable software development.

Introduction:

Design patterns are reusable solutions to common software design problems. They provide a blueprint for solving problems that have been encountered in the past, and they can be adapted and applied to new situations.

In the context of Python, design patterns can be applied to solve various problems that arise while writing software in this language. Python's dynamic and flexible nature makes it well-suited for implementing design patterns.

Some of the most commonly used design patterns in Python include:

  1. Factory Pattern: This pattern provides a way to create objects without specifying the exact class of object that will be created.

  2. Singleton Pattern: This pattern ensures that a class has only one instance and provides a global point of access to this instance.

  3. Observer Pattern: This pattern defines a one-to-many relationship between objects, where one object is the subject and the others are observers. The subject sends notifications to the observers when its state changes.

  4. Decorator Pattern: This pattern allows adding new behavior to objects dynamically, by wrapping them in decorator objects that provide the desired behavior.

  5. Adapter Pattern: This pattern converts the interface of a class into another interface that the client expects.

These are just a few of the many design patterns that can be used in Python. It's important to note that not every problem requires a design pattern, and sometimes a simple solution may be more appropriate. However, understanding design patterns can be a valuable tool for writing clean, maintainable, and scalable code.

In this article, I will write about Decorator Patterns.

What is Decorator in python?

In Python, a decorator is a special type of function or class that can be used to modify the behavior of other functions or classes. Decorators are applied to functions or classes by using the "@" symbol followed by the name of the decorator.

def uppercase_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

@uppercase_decorator
def say_hello():
    return "hello"

print(say_hello()) # Output: HELLO

Here is the above code example, the uppercase_decorator the function takes a function say_hello as input and returns a new function wrapper that takes the result of the original function and returns it in uppercase letters.

Decorator patterns

The Python decorator pattern is a design pattern that allows programmers to add new functionality to an existing object, without modifying its structure. This is achieved through the use of decorators, which are special functions that take another function as input and add new behavior to it.

Let's understand with an example

def decorator_function(original_function):
    def wrapper_function(*args, **kwargs):
        print(f"Wrapper executed before {original_function.__name__}")
        result = original_function(*args, **kwargs)
        print(f"Wrapper executed after {original_function.__name__}")
        return result
    return wrapper_function

@decorator_function
def display():
    print("Display function ran")

display()

# Output:
# Wrapper executed before display
# Display function ran
# Wrapper executed after display

the decorator_function takes a function as input (display), and returns a new function (wrapper_function) that has the additional behavior of printing a message before and after the original function is executed. The @decorator_function the syntax is a shorthand for saying display = decorator_function(display). When the display the function is called, it is actually called the wrapper_function , which in turn calls the original display function and adds the additional behavior.

It's important to note that decorators are applied in the order in which they appear. So, if you have multiple decorators for a single function, the innermost decorator will be applied first, and the outermost decorator will be applied last. Here's an example.

def strong_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return f"<strong>{result}</strong>"
    return wrapper

def italic_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return f"<em>{result}</em>"
    return wrapper

@strong_decorator
@italic_decorator
def say_hello():
    return "hello"

print(say_hello()) # Output: <strong><em>hello</em></strong>

So, you can see in the above example, the say_hello the function is decorated by both the strong_decorator and italic_decorator functions. The italic_decorator is applied first, so the output of say_hello is first decorated with <em> tags, and then with <strong> tags.

Conclusion:

The Python decorator pattern is a powerful tool for extending the behavior of objects in a clean and maintainable way. It allows you to add new functionality to existing objects without modifying their structure and can be used to implement a wide range of functionality, such as logging, timing, authentication, and more.

Thank you for reading, If you like my article, please like, share, and follow me on Twitter and LinkedIn.