Full analysis of Python design patterns

Design patterns are not rigid code templates, but a repeatedly proven framework of ideas for solving common software problems. Python's dynamic types, higher-order functions, metaclasses and other features can just make these ideas more "lightweight" than static languages, and even "invisibly" integrated into daily code.

This article uses Python native syntax or minimalist code to quickly dismantle three types of core design patterns and help you avoid the misunderstanding of "using patterns for the sake of using patterns".

Complete reference implementation library (GitHub Wanxing+): faif/python-patterns


1. Creational Patterns

Core logic: Do not let the caller directlynewOr create objects - encapsulate the creation details, which not only decouples the system and specific classes, but also flexibly controls the method, timing and quantity of instantiation.

1. Factory Method

There is no need to explicitly know the specific class name, "get" the object from the factory function through input parameters or current configuration.

# Python 可以直接用函数当“工厂”,不需要定义单独的工厂类!
def get_serializer(fmt: str):
    """根据格式字符串返回对应的序列化函数"""
    match fmt:
        case "json":
            return lambda data: f"JSON: {data}"
        case "xml":
            return lambda data: f"XML: {data}"
        case _:
            return str  # 默认返回字符串序列化

# 调用方完全不用关心具体序列化类的实现
if __name__ == "__main__":
    serializer = get_serializer("json")
    print(serializer({"name": "Python"}))  # JSON: {'name': 'Python'}
  • Advantages: Shield object creation details, add new formats (such asyaml) only needs to modify the factory function and does not touch the caller code.
  • Disadvantages: Simple scenarios (only 2 optional objects) will increase a little logic cost.
  • 📌 Applicable to: Scenarios where instances or tool classes need to be dynamically selected based on configuration.

2. Abstract Factory

The "upgrade model" of the factory is responsible for producing a complete set of related and compatible product families (such as buttons, backgrounds, and fonts in a set of skins) to ensure that they will not "mix and match errors."

from dataclasses import dataclass

# 先定义“产品族”的组成部分
@dataclass
class Button:
    text: str
    theme: str

@dataclass
class Background:
    color: str

# 具体产品
class DarkButton(Button):
    theme = "dark"

class LightButton(Button):
    theme = "light"

class DarkBackground(Background):
    color = "#1a1a1a"

class LightBackground(Background):
    color = "#ffffff"

# 抽象工厂的 Python 极简实现:用类本身当工厂!
class DarkThemeFactory:
    @staticmethod
    def create_button(text: str) -> Button:
        return DarkButton(text=text, theme="dark")

    @staticmethod
    def create_background() -> Background:
        return DarkBackground()

class LightThemeFactory:
    @staticmethod
    def create_button(text: str) -> Button:
        return LightButton(text=text, theme="light")

    @staticmethod
    def create_background() -> Background:
        return LightBackground()

# 使用:只需选择一次工厂,后续所有组件都是配套的
def setup_ui(factory):
    btn = factory.create_button("开始")
    bg = factory.create_background()
    print(f"按钮: {btn}, 背景: {bg}")

setup_ui(DarkThemeFactory)
  • Advantages: Force the product family to have a unified style, and there will be no low-level errors of "light-colored buttons with black backgrounds".
  • Disadvantages: Expanding the "dimensions of the product family" (such as adding rounded or right angle attributes) is very troublesome and requires changing all factory classes.
  • 📌 Applicable to: The connection and query tools are generated when changing the skin system and supporting multiple databases (MySQL / PostgreSQL).

3. Singleton mode (Singleton)

Ensure that a certain class has only one instance globally and provide a unified access entrance. There are many ways to implement Python, here we use the simplest one__new__Magic method:

class AppConfig:
    _instance = None   # 私有静态变量,存放唯一实例
    _data = None       # 存放配置数据

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._data = {           # 仅在首次创建时初始化
                "api_key": "123456",
                "timeout": 30
            }
        return cls._instance

# 验证单例
cfg1 = AppConfig()
cfg2 = AppConfig()
print(cfg1 is cfg2)  # True,是同一个对象
  • Advantages: Save memory (such as database connection pool, global log manager) and avoid repeated initialization.
  • Disadvantages: It violates the single responsibility principle (managing both its own creation and business data), and requires locking to ensure security in a multi-threaded environment.
  • 📌 Applicable to: Configuration management, log system, global unique resource manager.

2. Structural Patterns

Core logic: How to flexibly combine classes or objects to achieve greater functions, instead of using an "inheritance tree" that becomes unmaintainable.

4. Decorator mode (Decorator)

Python comes with@decoratorSyntactic sugar fits this pattern perfectly! **Dynamicly "add functionality" to an object or function without modifying its original source code. **

# 定义装饰器(用函数充当装饰器)
def bold(func):
    """给返回值加粗体标签"""
    def wrapper(*args, **kwargs):
        return f"<b>{func(*args, **kwargs)}</b>"
    return wrapper

def italic(func):
    """给返回值加斜体标签"""
    def wrapper(*args, **kwargs):
        return f"<i>{func(*args, **kwargs)}</i>"
    return wrapper

# 叠加装饰器!顺序很重要:自下而上执行
@bold
@italic
def say_hello(name: str) -> str:
    return f"Hello, {name}!"

# 测试
print(say_hello("Python"))  # <b><i>Hello, Python!</i></b>
  • Advantages: More flexible than inheritance (any number of decorators can be superimposed, the order is adjustable), and it fully complies with the opening and closing principle.
  • Disadvantages: Multiple levels of nesting will make debugging difficult (the error stack may point towrapper, not intuitive).
  • 📌 Applicable to: Logging, performance monitoring, permission verification, caching and other aspects of functions.

5. Proxy mode (Proxy)

Add a "substitute" to the target object. The caller accesses the substitute first. The substitute can control access permissions, delay loading of the target object, and even cache results.

# 目标对象:加载图片很耗时
class RealImage:
    def __init__(self, filename: str):
        self.filename = filename
        self._load_from_disk()   # 初始化时就加载

    def _load_from_disk(self):
        print(f"正在从磁盘加载图片:{self.filename}")

    def display(self):
        print(f"正在显示图片:{self.filename}")

# 代理对象:延迟加载,只有第一次 display 才真正加载
class ProxyImage:
    def __init__(self, filename: str):
        self.filename = filename
        self._real_image = None   # 初始不加载

    def display(self):
        if self._real_image is None:
            self._real_image = RealImage(self.filename)
        self._real_image.display()

# 测试
img = ProxyImage("cat.jpg")  # 这里没有输出加载信息
print("准备显示图片...")
img.display()  # 第一次显示才加载
img.display()  # 第二次直接显示,不用再加载
  • Advantages: Control access, lazy loading, caching results, and hiding the implementation of real objects.
  • Disadvantages: A layer of middle layer is added, and there will be a very slight loss in calling speed.
  • 📌 Applicable to: Image lazy loading, remote service proxy, security control (check user permissions before calling the real object).

3. Behavioral Patterns

Core logic: Focus on how objects communicate and collaborate to make interactions clearer and easier to expand.

6. Iterator pattern (Iterator)

Python native support! No need to manually define complex iterator classes (unless you want to customize traversal logic) - just implement__iter__and__next__Magic method, or directly use the built-initer()next()

# 1. 内置集合(列表、字典、字符串)都是可迭代的
for num in [1, 2, 3]:
    print(num)

# 2. 自定义一个简单的迭代器:遍历斐波那契数列的前 N 项
class FibonacciIterator:
    def __init__(self, n: int):
        self.n = n           # 前 N 项
        self.a, self.b = 0, 1   # 初始两项
        self.count = 0       # 已遍历的数量

    def __iter__(self):
        return self   # 迭代器本身要返回自己

    def __next__(self):
        if self.count >= self.n:
            raise StopIteration   # 遍历结束抛出异常
        self.count += 1
        self.a, self.b = self.b, self.a + self.b
        return self.a

# 测试自定义迭代器
for fib in FibonacciIterator(5):
    print(fib)  # 1 1 2 3 5
  • Advantages: Access collection contents without exposing the internal structure, and support multiple traversal methods (such as forward, reverse, depth first, etc.).
  • Disadvantages: For simple lists or dictionaries, manually implementing iterators would be redundant.
  • 📌 Applicable to: Traverse database query results, process very large streaming files, and customize data structures.

This article only covers the 6 most commonly used design patterns, and the rest can be studied in depth with the reference code library at the beginning. Remember: Patterns are tools, not purposes - when you encounter a problem, first think about "Is there any ready-made pattern that can be applied", and then decide whether to introduce it and how to use it in the most Pythonic way.