Everything You Need to Know About @Decorators in Python with Examples

winstonmhango23

7 min read

Views: 182

Decorators in Python are a powerful and expressive tool that allows for the modification of functions or methods using other functions

Decorators in Python are a powerful and expressive tool that allows for the modification of functions or methods using other functions. They provide a way to extend and alter the behavior of callable objects (functions, methods, or classes) without permanently modifying the objects themselves. This blog post will explore decorators in depth, covering their syntax, use cases, and technical details, with comprehensive examples using a banking system scenario.

Table of Contents

  1. Introduction to Decorators
  2. Basic Syntax and Usage
  3. Function Decorators
  4. Class Decorators
  5. Decorators with Arguments
  6. Practical Examples: Bank Account System
  7. Multiple Inheritance with Decorators
  8. Conclusion

Introduction to Decorators

Decorators are a design pattern in Python that allows a user to add new functionality to an existing object without modifying its structure. They are typically used to extend the behavior of functions or methods in a clean and readable way.

Key Concepts

  • Higher-Order Functions: Functions that operate on other functions, either by taking them as arguments or by returning them.
  • First-Class Functions: Functions in Python are treated as first-class citizens, meaning they can be passed around and used as arguments just like any other object (string, integer, etc.).

Basic Syntax and Usage

The basic syntax for a decorator is to define a function that returns another function. Here’s a simple example:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

Output

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

In this example, the my_decorator function is applied to the say_hello function using the @ syntax.

Function Decorators

Function decorators are the most common use of decorators. They can be used to add functionality such as logging, timing, or access control to existing functions.

Example: Logging Decorator

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Function '{func.__name__}' is called with arguments: {args} {kwargs}")
        result = func(*args, **kwargs)
        print(f"Function '{func.__name__}' returned: {result}")
        return result
    return wrapper

@log_decorator
def add(a, b):
    return a + b

add(3, 4)

Output

Function 'add' is called with arguments: (3, 4) {}
Function 'add' returned: 7

Class Decorators

Class decorators are used to modify or extend the behavior of classes. They work similarly to function decorators but are applied to class definitions.

Example: Adding Attributes to a Class

def add_attributes(cls):
    cls.new_attribute = "This is a new attribute"
    return cls

@add_attributes
class MyClass:
    pass

obj = MyClass()
print(obj.new_attribute)

Output

This is a new attribute

Decorators with Arguments

Sometimes, you may want to pass arguments to your decorators. This can be achieved by nesting functions within the decorator.

Example: Timing Decorator with Arguments

import time

def timer_decorator(unit="seconds"):
    def decorator(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            duration = end_time - start_time
            if unit == "milliseconds":
                duration *= 1000
            print(f"Function '{func.__name__}' took {duration} {unit}")
            return result
        return wrapper
    return decorator

@timer_decorator(unit="milliseconds")
def slow_function():
    time.sleep(1)

slow_function()

Output

Function 'slow_function' took 1000.123456 milliseconds

Practical Examples: Bank Account System

Now, let's implement a banking system with different account types and loan conditions, using decorators to manage different interest rates and logging functionalities.

Basic Classes

class BankAccount:
    def __init__(self, account_id, name, balance=0):
        self.account_id = account_id
        self.name = name
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
        else:
            print("Insufficient funds")

    def get_balance(self):
        return self.balance

class SavingsAccount(BankAccount):
    def __init__(self, account_id, name, balance=0):
        super().__init__(account_id, name, balance)
        self.interest_rate = 0.02  # 2% interest rate

    def apply_interest(self):
        self.balance += self.balance * self.interest_rate

class LoanAccount(BankAccount):
    def __init__(self, account_id, name, balance=0):
        super().__init__(account_id, name, balance)
        self.loan_amount = 0

    def request_loan(self, amount):
        self.loan_amount += amount

    def repay_loan(self, amount):
        if amount <= self.loan_amount:
            self.loan_amount -= amount
        else:
            print("Overpayment")

Decorators

Logging Decorator

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with {args} and {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned {result}")
        return result
    return wrapper

Interest Rate Decorator

def interest_rate_decorator(rate):
    def decorator(func):
        def wrapper(*args, **kwargs):
            account = args[0]
            account.balance += account.balance * rate
            print(f"Interest applied at rate {rate*100}%")
            return func(*args, **kwargs)
        return wrapper
    return decorator

Using the Decorators

@log_decorator
@interest_rate_decorator(rate=0.05)  # 5% interest rate
def apply_interest(account):
    pass

# Example usage
savings = SavingsAccount("001", "John Doe", 1000)
apply_interest(savings)
print(savings.get_balance())  # Output: 1050.0

Multiple Inheritance with Decorators

Let's create a PremiumAccount that inherits from both SavingsAccount and LoanAccount and applies a decorator to modify its behavior.

class PremiumAccount(SavingsAccount, LoanAccount):
    def __init__(self, account_id, name, balance=0):
        SavingsAccount.__init__(self, account_id, name, balance)
        LoanAccount.__init__(self, account_id, name, balance)

@log_decorator
@interest_rate_decorator(rate=0.03)  # 3% interest rate
def apply_premium_interest(account):
    pass

# Example usage
premium = PremiumAccount("002", "Jane Smith", 2000)
apply_premium_interest(premium)
print(premium.get_balance())  # Output: 2060.0
premium.request_loan(5000)
print(premium.loan_amount)  # Output: 5000

Conclusion

Decorators in Python are a versatile tool that allows you to extend and modify the behavior of functions and methods. They promote code reusability and separation of concerns by enabling you to add functionality to existing code without modifying it. Understanding decorators can greatly enhance your ability to write clean, maintainable, and efficient Python code.

In this blog post, we explored the basics of decorators, function decorators, class decorators, decorators with arguments, and practical examples using a banking system. We also looked at how to use decorators in conjunction with multiple inheritance. By mastering decorators, you can take your Python programming skills to the next level.

Recent Related Posts

Related Posts

Creating and uploading a Python package to the PyPi: Part 3 Creating The package and uploading to pypi.org

In this part of the series, we will walk you through the final steps of creating a Python package and uploading it to PyPi. We will use the example package bank_creator that we discussed in Part 2. The full code for this package can be found on GitHub.

Read More

Creating and uploading a Python package to the PyPi: Part 2 Creating Git repository and uploading to github

Read More

Creating and uploading a Python package to the PyPi: Part 1 Basics

Read More

© 2024 .