Popular Python Design Patterns

Python is one of the hottest programming languages in the world right now. According to StackOverflow’s Developer Survey 2021, Python is the third most popular programming language. It is primarily due to its easy-going syntax coupled with powerful dynamic typing and binding. 

In this article, we will focus on building efficient and scalable applications in Python using popular design patterns. Design patterns are standards or conventions used to solve commonly occurring problems. Given Python’s highly flexible nature, design patterns are essential.

You can use these links to navigate the guide:

Popular Python Patterns

Following are some of the commonly used design patterns in the software industry.

Singleton

The Singleton design pattern is one of the simplest yet highly beneficial creational design patterns. This pattern ensures that only one instance of a kind exists. Whenever some client code asks for an instance of a class, the same instance is returned every time.

Here’s how you can implement a Singleton class in Python:

class FooMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):

        if cls not in cls._instances:
            instance = super().__call__(*args, **kwargs)
            cls._instances[cls] = instance
        return cls._instances[cls]


class Foo(metaclass=FooMeta):
    def some_func_here(self):
        return 42


if __name__ == "__main__":

    foo1 = Foo()
    foo2 = Foo()

    if id(foo1) == id(foo2):
        print("Both variables refer to the same object")
    else:
        print("Both variables refer to different objects")

The output will be:

Both variables refer to the same object

Factory

The Factory design pattern is a creational design pattern that enables you to create objects in a base class but allows the children classes to override the object creation method and alter the type of objects created.

Here’s how you can implement Factory design pattern in Python:

from __future__ import annotations
from abc import ABC, abstractmethod


class Creator(ABC):

    @abstractmethod
    def factory_method(self):
        pass

    def some_operation(self) -> str:

        product = self.factory_method()

        result = f"Creator: The same creator's code has just worked with {product.operation()}"

        return result

class ConcreteCreator1(Creator):

    def factory_method(self) -> Product:
        return ConcreteProduct1()


class ConcreteCreator2(Creator):
    def factory_method(self) -> Product:
        return ConcreteProduct2()


class Product(ABC):
   
    @abstractmethod
    def operation(self) -> str:
        pass


class ConcreteProduct1(Product):
    def operation(self) -> str:
        return "CP1"


class ConcreteProduct2(Product):
    def operation(self) -> str:
        return "CP2"


def client(creator: Creator) -> None:
    print(creator.some_operation())


if __name__ == "__main__":
    print("App: Launched with the ConcreteCreator1.")
    client(ConcreteCreator1())

    print("App: Launched with the ConcreteCreator2.")
    client(ConcreteCreator2())


Here’s the output:

App: Launched with the ConcreteCreator1.
Creator: The same creator's code has just worked with CP1
App: Launched with the ConcreteCreator2.
Creator: The same creator's code has just worked with CP2

Proxy

Proxy is a structural design pattern that acts as a placeholder for a target object. It has the same interface and returns the same results as the target class. It helps you to add some logic before a job is passed to the target class or is completed and returned by the target class.

Here’s how you can implement the Proxy design pattern in Python:

from abc import ABC, abstractmethod


class Target(ABC):

    @abstractmethod
    def execute(self) -> None:
        pass


class RealTarget(Target):
    def execute(self) -> None:
        print("Real Target executed")


class Proxy(Target):

    def __init__(self, real_target: RealTarget) -> None:
        self._real_target = real_target

    def execute(self) -> None:

        if self.check_access():
            self._real_target.execute()
            self.log_access()

    def check_access(self) -> bool:
        print("Proxy: Checking access before executing Real Target")
        return True

    def log_access(self) -> None:
        print("Proxy: Logging the time of execution.", end="")


def client(target: Target) -> None:
    target.execute()



if __name__ == "__main__":
    print("Client: Executing the client code with a real target:")
    real_target = RealTarget()
    client(real_target)

    print("")

    print("Client: Executing the same code with a proxy:")
    proxy = Proxy(real_target)
    client(proxy)

Here’s the output:

Client: Executing the client code with a real target:
Real Target executed

Client: Executing the same code with a proxy:
Proxy: Checking access before executing Real Target
Real Target executed

Facade

Facade is a popular structural design pattern that helps to simplify complex systems of classes, libraries, or frameworks. Facade acts as an interface between the client and the complex system enabling a simpler way of access.

Here’s how you can implement Facade in a Python program:

from __future__ import annotations

class FacadeTest:

    def __init__(self, subsystem1: Subsystem1, subsystem2: Subsystem2) -> None:

        self._subsystem1 = subsystem1 or Subsystem1()
        self._subsystem2 = subsystem2 or Subsystem2()

    def operation(self) -> str:

        results = []
        results.append(self._subsystem1.operation1())
        results.append(self._subsystem2.operation1())
       
        results.append(self._subsystem1.operation2())
        results.append(self._subsystem2.operation2())
        return "\n".join(results)


class Dependency1:

    def operation1(self) -> str:
        return "Dependency1 available"

    def operation2(self) -> str:
        return "Dependency1 started"


class Dependency2:

    def operation1(self) -> str:
        return "Dependency2 available"

    def operation2(self) -> str:
        return "Dependency2 started"


def client(facade_test: FacadeTest) -> None:
    # facade simplifies the dependencies for the client here:
    print(facade_test.operation(), end="")


if __name__ == "__main__":
    dep1 = Dependency1()
    dep2 = Dependency2()
    facade = FacadeTest(dep1, dep2)
    client(facade)

The output will be:

Dependency1 available
Dependency2 available
Dependency1 started
Dependency2 started

Over to You

Design patterns are a great way to reinforce your application’s architecture with well-tested coding standards. Design patterns provide you with quick and reliable solutions to the most common software engineering problems.

While Python is popular for its extremely simplified syntax and programming conventions, design patterns can help you gain the control you need over your code’s design.

Python Design Patterns Frequently Asked Questions

Here are answers to some commonly asked questions about Python design patterns:

What is the best design pattern in Python?

Each Python design pattern has its own use case. Ranking by frequency of usage, creational design patterns such as Singleton and Factory are quite popular and useful to most programming projects.

Does Python have design patterns?

Yes, Python has design patterns. In fact, design patterns are simply ways to solve problems; they are independent of any programming language. You can implement most design patterns in a wide range of programming languages.

What are design patterns in Python?

Python design patterns are tried and tested ways of solving common problems in Python. They are categorized on the basis of their uses—creational, behavioral, structural, etc.

What are different design patterns in Python?

There is a wide range of design patterns in Python. Some of the most commonly used design patterns are

What is the use of duck typing in Python?

Duck typing in Python is used to enable support for dynamic typing. The basic concept behind this principle is that you should be concerned about the behavior of an object rather than its type.

What is Python debugging?

Python debugging refers to the process of resolving bugs (issues) that occur in Python programs. You can set breakpoints in your code and jump through them to view where your logic is flawed or your variables are set wrong.

Feel free to check out Scout’s APM offerings across tech stacks and subscribe to Scout’s monthly newsletter to learn more on similar technical concepts!