DEV Community

qing
qing

Posted on

Stop Writing Repetitive Python Code — Use These 5 Patterns

As Python developers, we've all been there - writing the same code over and over again, wishing there was a way to simplify our workflow and make our code more efficient. The good news is that there are several patterns and techniques that can help you avoid repetitive code and make your life easier. In this article, we'll explore five patterns that you can use to stop writing repetitive Python code and take your coding skills to the next level.

1. The Decorator Pattern

The decorator pattern is a powerful tool in Python that allows you to modify the behavior of a function or class without changing its implementation. It's often used for logging, authentication, and caching, among other things. By using decorators, you can avoid repetitive code and make your functions more reusable.

Here's an example of a simple decorator that logs the execution time of a function:

import time
from functools import wraps

def timer_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {end_time - start_time} seconds to execute.")
        return result
    return wrapper

@timer_decorator
def example_function():
    time.sleep(2)
    print("Function executed.")

example_function()
Enter fullscreen mode Exit fullscreen mode

In this example, the timer_decorator function takes another function as an argument and returns a new function that wraps the original function. The wrapper function logs the execution time of the original function and then calls it.

2. The Factory Pattern

The factory pattern is a creational pattern that provides a way to create objects without specifying the exact class of object that will be created. It's often used when you need to create objects that have similar characteristics but differ in some way. By using factories, you can avoid repetitive code and make your object creation more flexible.

Here's an example of a simple factory function that creates different types of database connections:

class DatabaseConnection:
    def __init__(self, db_type):
        self.db_type = db_type

    def connect(self):
        if self.db_type == "mysql":
            print("Connecting to MySQL database...")
        elif self.db_type == "postgres":
            print("Connecting to Postgres database...")
        else:
            raise ValueError("Invalid database type.")

def database_factory(db_type):
    return DatabaseConnection(db_type)

mysql_connection = database_factory("mysql")
mysql_connection.connect()

postgres_connection = database_factory("postgres")
postgres_connection.connect()
Enter fullscreen mode Exit fullscreen mode

In this example, the database_factory function creates a DatabaseConnection object based on the db_type argument. The DatabaseConnection class has a connect method that behaves differently depending on the database type.

3. The Template Method Pattern

The template method pattern is a behavioral pattern that provides a way to define a skeleton for an algorithm in a superclass, while allowing subclasses to customize certain steps of the algorithm. It's often used when you need to implement a complex algorithm that has some variable steps. By using template methods, you can avoid repetitive code and make your algorithms more flexible.

Here's an example of a simple template method that implements a data processing pipeline:

from abc import ABC, abstractmethod

class DataProcessor(ABC):
    def process_data(self, data):
        self.load_data(data)
        self.transform_data()
        self.save_data()

    @abstractmethod
    def load_data(self, data):
        pass

    @abstractmethod
    def transform_data(self):
        pass

    @abstractmethod
    def save_data(self):
        pass

class CSVDataProcessor(DataProcessor):
    def load_data(self, data):
        print("Loading data from CSV file...")

    def transform_data(self):
        print("Transforming data...")

    def save_data(self):
        print("Saving data to CSV file...")

class JSONDataProcessor(DataProcessor):
    def load_data(self, data):
        print("Loading data from JSON file...")

    def transform_data(self):
        print("Transforming data...")

    def save_data(self):
        print("Saving data to JSON file...")

csv_processor = CSVDataProcessor()
csv_processor.process_data("data.csv")

json_processor = JSONDataProcessor()
json_processor.process_data("data.json")
Enter fullscreen mode Exit fullscreen mode

In this example, the DataProcessor class defines a template method process_data that calls three abstract methods: load_data, transform_data, and save_data. The CSVDataProcessor and JSONDataProcessor classes implement these abstract methods to customize the data processing pipeline.

4. The Observer Pattern

The observer pattern is a behavioral pattern that provides a way to notify objects of changes to other objects without having a direct reference to one another. It's often used in event-driven programming and GUI development. By using observers, you can avoid repetitive code and make your objects more decoupled.

Here's an example of a simple observer pattern that notifies objects of changes to a subject:

class Subject:
    def __init__(self):
        self.observers = []

    def register_observer(self, observer):
        self.observers.append(observer)

    def notify_observers(self, message):
        for observer in self.observers:
            observer.update(message)

class Observer:
    def update(self, message):
        print(f"Received message: {message}")

subject = Subject()
observer1 = Observer()
observer2 = Observer()

subject.register_observer(observer1)
subject.register_observer(observer2)

subject.notify_observers("Hello, world!")
Enter fullscreen mode Exit fullscreen mode

In this example, the Subject class has a list of observers and provides methods to register and notify observers. The Observer class has an update method that is called when the subject notifies its observers.

5. The Singleton Pattern

The singleton pattern is a creational pattern that ensures a class has only one instance and provides a global point of access to it. It's often used in logging, configuration management, and database connections. By using singletons, you can avoid repetitive code and make your objects more efficient.

Here's an example of a simple singleton pattern that implements a logger:

class Logger:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(Logger, cls).__new__(cls)
        return cls._instance

    def log(self, message):
        print(f"Log: {message}")

logger1 = Logger()
logger2 = Logger()

logger1.log("Hello, world!")
logger2.log("Hello, again!")
Enter fullscreen mode Exit fullscreen mode

In this example, the Logger class has a private class variable _instance that stores the single instance of the class. The __new__ method ensures that only one instance is created.

Practical Tips

To get the most out of these patterns, here are some practical tips to keep in mind:

  • Start by identifying areas of your code where you're repeating yourself. Look for functions or classes that have similar behavior or structures.
  • Choose the pattern that best fits the problem you're trying to solve. Don't be afraid to experiment and try out different patterns to see what works best.
  • Keep your code organized and

📧 Found this useful? Follow me for more Python tips and automation tricks!


If you found this useful, you might like Python Interview Prep Guide — a practical resource that takes things a step further. At $24.99 it's a solid investment for your toolkit.

Top comments (0)