python-design-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Python Design Patterns

Python设计模式

Write maintainable Python code using fundamental design principles. These patterns help you build systems that are easy to understand, test, and modify.
使用基础设计原则编写可维护的Python代码。这些模式能帮助你构建易于理解、测试和修改的系统。

When to Use This Skill

何时使用这些设计原则

  • Designing new components or services
  • Refactoring complex or tangled code
  • Deciding whether to create an abstraction
  • Choosing between inheritance and composition
  • Evaluating code complexity and coupling
  • Planning modular architectures
  • 设计新组件或服务
  • 重构复杂或混乱的代码
  • 决定是否创建抽象
  • 在继承和组合之间做选择
  • 评估代码复杂度与耦合度
  • 规划模块化架构

Core Concepts

核心概念

1. KISS (Keep It Simple)

1. KISS(保持简单)

Choose the simplest solution that works. Complexity must be justified by concrete requirements.
选择可行的最简单解决方案。复杂度必须有具体需求作为依据。

2. Single Responsibility (SRP)

2. 单一职责原则(SRP)

Each unit should have one reason to change. Separate concerns into focused components.
每个单元应该只有一个修改的理由。将关注点拆分到专注的组件中。

3. Composition Over Inheritance

3. 组合优于继承

Build behavior by combining objects, not extending classes.
通过组合对象来构建行为,而非扩展类。

4. Rule of Three

4. 三次原则

Wait until you have three instances before abstracting. Duplication is often better than premature abstraction.
等到出现三个相同场景时再进行抽象。重复代码通常比过早抽象更好。

Quick Start

快速入门

python
undefined
python
undefined

Simple beats clever

Simple beats clever

Instead of a factory/registry pattern:

Instead of a factory/registry pattern:

FORMATTERS = {"json": JsonFormatter, "csv": CsvFormatter}
def get_formatter(name: str) -> Formatter: return FORMATTERSname
undefined
FORMATTERS = {"json": JsonFormatter, "csv": CsvFormatter}
def get_formatter(name: str) -> Formatter: return FORMATTERSname
undefined

Fundamental Patterns

基础模式

Pattern 1: KISS - Keep It Simple

模式1:KISS - 保持简单

Before adding complexity, ask: does a simpler solution work?
python
undefined
在增加复杂度之前,先问自己:有没有更简单的解决方案?
python
undefined

Over-engineered: Factory with registration

Over-engineered: Factory with registration

class OutputFormatterFactory: _formatters: dict[str, type[Formatter]] = {}
@classmethod
def register(cls, name: str):
    def decorator(formatter_cls):
        cls._formatters[name] = formatter_cls
        return formatter_cls
    return decorator

@classmethod
def create(cls, name: str) -> Formatter:
    return cls._formatters[name]()
@OutputFormatterFactory.register("json") class JsonFormatter(Formatter): ...
class OutputFormatterFactory: _formatters: dict[str, type[Formatter]] = {}
@classmethod
def register(cls, name: str):
    def decorator(formatter_cls):
        cls._formatters[name] = formatter_cls
        return formatter_cls
    return decorator

@classmethod
def create(cls, name: str) -> Formatter:
    return cls._formatters[name]()
@OutputFormatterFactory.register("json") class JsonFormatter(Formatter): ...

Simple: Just use a dictionary

Simple: Just use a dictionary

FORMATTERS = { "json": JsonFormatter, "csv": CsvFormatter, "xml": XmlFormatter, }
def get_formatter(name: str) -> Formatter: """Get formatter by name.""" if name not in FORMATTERS: raise ValueError(f"Unknown format: {name}") return FORMATTERSname

The factory pattern adds code without adding value here. Save patterns for when they solve real problems.
FORMATTERS = { "json": JsonFormatter, "csv": CsvFormatter, "xml": XmlFormatter, }
def get_formatter(name: str) -> Formatter: """Get formatter by name.""" if name not in FORMATTERS: raise ValueError(f"Unknown format: {name}") return FORMATTERSname

此处工厂模式增加了代码量却没有带来实际价值。只有当模式能解决实际问题时再使用它们。

Pattern 2: Single Responsibility Principle

模式2:单一职责原则

Each class or function should have one reason to change.
python
undefined
每个类或函数应该只有一个修改的理由。
python
undefined

BAD: Handler does everything

BAD: Handler does everything

class UserHandler: async def create_user(self, request: Request) -> Response: # HTTP parsing data = await request.json()
    # Validation
    if not data.get("email"):
        return Response({"error": "email required"}, status=400)

    # Database access
    user = await db.execute(
        "INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *",
        data["email"], data["name"]
    )

    # Response formatting
    return Response({"id": user.id, "email": user.email}, status=201)
class UserHandler: async def create_user(self, request: Request) -> Response: # HTTP parsing data = await request.json()
    # Validation
    if not data.get("email"):
        return Response({"error": "email required"}, status=400)

    # Database access
    user = await db.execute(
        "INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *",
        data["email"], data["name"]
    )

    # Response formatting
    return Response({"id": user.id, "email": user.email}, status=201)

GOOD: Separated concerns

GOOD: Separated concerns

class UserService: """Business logic only."""
def __init__(self, repo: UserRepository) -> None:
    self._repo = repo

async def create_user(self, data: CreateUserInput) -> User:
    # Only business rules here
    user = User(email=data.email, name=data.name)
    return await self._repo.save(user)
class UserHandler: """HTTP concerns only."""
def __init__(self, service: UserService) -> None:
    self._service = service

async def create_user(self, request: Request) -> Response:
    data = CreateUserInput(**(await request.json()))
    user = await self._service.create_user(data)
    return Response(user.to_dict(), status=201)

Now HTTP changes don't affect business logic, and vice versa.
class UserService: """Business logic only."""
def __init__(self, repo: UserRepository) -> None:
    self._repo = repo

async def create_user(self, data: CreateUserInput) -> User:
    # Only business rules here
    user = User(email=data.email, name=data.name)
    return await self._repo.save(user)
class UserHandler: """HTTP concerns only."""
def __init__(self, service: UserService) -> None:
    self._service = service

async def create_user(self, request: Request) -> Response:
    data = CreateUserInput(**(await request.json()))
    user = await self._service.create_user(data)
    return Response(user.to_dict(), status=201)

现在HTTP相关的修改不会影响业务逻辑,反之亦然。

Pattern 3: Separation of Concerns

模式3:关注点分离

Organize code into distinct layers with clear responsibilities.
┌─────────────────────────────────────────────────────┐
│  API Layer (handlers)                                │
│  - Parse requests                                    │
│  - Call services                                     │
│  - Format responses                                  │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│  Service Layer (business logic)                      │
│  - Domain rules and validation                       │
│  - Orchestrate operations                            │
│  - Pure functions where possible                     │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│  Repository Layer (data access)                      │
│  - SQL queries                                       │
│  - External API calls                                │
│  - Cache operations                                  │
└─────────────────────────────────────────────────────┘
Each layer depends only on layers below it:
python
undefined
将代码组织成职责明确的不同层级。
┌─────────────────────────────────────────────────────┐
│  API 层(处理器)                                │
│  - 解析请求                                    │
│  - 调用服务                                     │
│  - 格式化响应                                  │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│  服务层(业务逻辑)                      │
│  - 领域规则与验证                       │
│  - 编排操作                            │
│  - 尽可能使用纯函数                     │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│  仓储层(数据访问)                      │
│  - SQL 查询                                       │
│  - 外部 API 调用                                │
│  - 缓存操作                                  │
└─────────────────────────────────────────────────────┘
每个层级仅依赖其下方的层级:
python
undefined

Repository: Data access

Repository: Data access

class UserRepository: async def get_by_id(self, user_id: str) -> User | None: row = await self._db.fetchrow( "SELECT * FROM users WHERE id = $1", user_id ) return User(**row) if row else None
class UserRepository: async def get_by_id(self, user_id: str) -> User | None: row = await self._db.fetchrow( "SELECT * FROM users WHERE id = $1", user_id ) return User(**row) if row else None

Service: Business logic

Service: Business logic

class UserService: def init(self, repo: UserRepository) -> None: self._repo = repo
async def get_user(self, user_id: str) -> User:
    user = await self._repo.get_by_id(user_id)
    if user is None:
        raise UserNotFoundError(user_id)
    return user
class UserService: def init(self, repo: UserRepository) -> None: self._repo = repo
async def get_user(self, user_id: str) -> User:
    user = await self._repo.get_by_id(user_id)
    if user is None:
        raise UserNotFoundError(user_id)
    return user

Handler: HTTP concerns

Handler: HTTP concerns

@app.get("/users/{user_id}") async def get_user(user_id: str) -> UserResponse: user = await user_service.get_user(user_id) return UserResponse.from_user(user)
undefined
@app.get("/users/{user_id}") async def get_user(user_id: str) -> UserResponse: user = await user_service.get_user(user_id) return UserResponse.from_user(user)
undefined

Pattern 4: Composition Over Inheritance

模式4:组合优于继承

Build behavior by combining objects rather than inheriting.
python
undefined
通过组合对象而非继承来构建行为。
python
undefined

Inheritance: Rigid and hard to test

Inheritance: Rigid and hard to test

class EmailNotificationService(NotificationService): def init(self): super().init() self._smtp = SmtpClient() # Hard to mock
def notify(self, user: User, message: str) -> None:
    self._smtp.send(user.email, message)
class EmailNotificationService(NotificationService): def init(self): super().init() self._smtp = SmtpClient() # Hard to mock
def notify(self, user: User, message: str) -> None:
    self._smtp.send(user.email, message)

Composition: Flexible and testable

Composition: Flexible and testable

class NotificationService: """Send notifications via multiple channels."""
def __init__(
    self,
    email_sender: EmailSender,
    sms_sender: SmsSender | None = None,
    push_sender: PushSender | None = None,
) -> None:
    self._email = email_sender
    self._sms = sms_sender
    self._push = push_sender

async def notify(
    self,
    user: User,
    message: str,
    channels: set[str] | None = None,
) -> None:
    channels = channels or {"email"}

    if "email" in channels:
        await self._email.send(user.email, message)

    if "sms" in channels and self._sms and user.phone:
        await self._sms.send(user.phone, message)

    if "push" in channels and self._push and user.device_token:
        await self._push.send(user.device_token, message)
class NotificationService: """Send notifications via multiple channels."""
def __init__(
    self,
    email_sender: EmailSender,
    sms_sender: SmsSender | None = None,
    push_sender: PushSender | None = None,
) -> None:
    self._email = email_sender
    self._sms = sms_sender
    self._push = push_sender

async def notify(
    self,
    user: User,
    message: str,
    channels: set[str] | None = None,
) -> None:
    channels = channels or {"email"}

    if "email" in channels:
        await self._email.send(user.email, message)

    if "sms" in channels and self._sms and user.phone:
        await self._sms.send(user.phone, message)

    if "push" in channels and self._push and user.device_token:
        await self._push.send(user.device_token, message)

Easy to test with fakes

Easy to test with fakes

service = NotificationService( email_sender=FakeEmailSender(), sms_sender=FakeSmsSender(), )
undefined
service = NotificationService( email_sender=FakeEmailSender(), sms_sender=FakeSmsSender(), )
undefined

Advanced Patterns

进阶模式

Pattern 5: Rule of Three

模式5:三次原则

Wait until you have three instances before abstracting.
python
undefined
等到出现三个相同场景时再进行抽象。
python
undefined

Two similar functions? Don't abstract yet

Two similar functions? Don't abstract yet

def process_orders(orders: list[Order]) -> list[Result]: results = [] for order in orders: validated = validate_order(order) result = process_validated_order(validated) results.append(result) return results
def process_returns(returns: list[Return]) -> list[Result]: results = [] for ret in returns: validated = validate_return(ret) result = process_validated_return(validated) results.append(result) return results
def process_orders(orders: list[Order]) -> list[Result]: results = [] for order in orders: validated = validate_order(order) result = process_validated_order(validated) results.append(result) return results
def process_returns(returns: list[Return]) -> list[Result]: results = [] for ret in returns: validated = validate_return(ret) result = process_validated_return(validated) results.append(result) return results

These look similar, but wait! Are they actually the same?

These look similar, but wait! Are they actually the same?

Different validation, different processing, different errors...

Different validation, different processing, different errors...

Duplication is often better than the wrong abstraction

Duplication is often better than the wrong abstraction

Only after a third case, consider if there's a real pattern

Only after a third case, consider if there's a real pattern

But even then, sometimes explicit is better than abstract

But even then, sometimes explicit is better than abstract

undefined
undefined

Pattern 6: Function Size Guidelines

模式6:函数大小指南

Keep functions focused. Extract when a function:
  • Exceeds 20-50 lines (varies by complexity)
  • Serves multiple distinct purposes
  • Has deeply nested logic (3+ levels)
python
undefined
保持函数专注。当函数出现以下情况时进行拆分:
  • 超过20-50行(根据复杂度有所不同)
  • 承担多个不同职责
  • 存在深度嵌套逻辑(3层及以上)
python
undefined

Too long, multiple concerns mixed

Too long, multiple concerns mixed

def process_order(order: Order) -> Result: # 50 lines of validation... # 30 lines of inventory check... # 40 lines of payment processing... # 20 lines of notification... pass
def process_order(order: Order) -> Result: # 50 lines of validation... # 30 lines of inventory check... # 40 lines of payment processing... # 20 lines of notification... pass

Better: Composed from focused functions

Better: Composed from focused functions

def process_order(order: Order) -> Result: """Process a customer order through the complete workflow.""" validate_order(order) reserve_inventory(order) payment_result = charge_payment(order) send_confirmation(order, payment_result) return Result(success=True, order_id=order.id)
undefined
def process_order(order: Order) -> Result: """Process a customer order through the complete workflow.""" validate_order(order) reserve_inventory(order) payment_result = charge_payment(order) send_confirmation(order, payment_result) return Result(success=True, order_id=order.id)
undefined

Pattern 7: Dependency Injection

模式7:依赖注入

Pass dependencies through constructors for testability.
python
from typing import Protocol

class Logger(Protocol):
    def info(self, msg: str, **kwargs) -> None: ...
    def error(self, msg: str, **kwargs) -> None: ...

class Cache(Protocol):
    async def get(self, key: str) -> str | None: ...
    async def set(self, key: str, value: str, ttl: int) -> None: ...

class UserService:
    """Service with injected dependencies."""

    def __init__(
        self,
        repository: UserRepository,
        cache: Cache,
        logger: Logger,
    ) -> None:
        self._repo = repository
        self._cache = cache
        self._logger = logger

    async def get_user(self, user_id: str) -> User:
        # Check cache first
        cached = await self._cache.get(f"user:{user_id}")
        if cached:
            self._logger.info("Cache hit", user_id=user_id)
            return User.from_json(cached)

        # Fetch from database
        user = await self._repo.get_by_id(user_id)
        if user:
            await self._cache.set(f"user:{user_id}", user.to_json(), ttl=300)

        return user
通过构造函数传递依赖以提升可测试性。
python
from typing import Protocol

class Logger(Protocol):
    def info(self, msg: str, **kwargs) -> None: ...
    def error(self, msg: str, **kwargs) -> None: ...

class Cache(Protocol):
    async def get(self, key: str) -> str | None: ...
    async def set(self, key: str, value: str, ttl: int) -> None: ...

class UserService:
    """Service with injected dependencies."""

    def __init__(
        self,
        repository: UserRepository,
        cache: Cache,
        logger: Logger,
    ) -> None:
        self._repo = repository
        self._cache = cache
        self._logger = logger

    async def get_user(self, user_id: str) -> User:
        # Check cache first
        cached = await self._cache.get(f"user:{user_id}")
        if cached:
            self._logger.info("Cache hit", user_id=user_id)
            return User.from_json(cached)

        # Fetch from database
        user = await self._repo.get_by_id(user_id)
        if user:
            await self._cache.set(f"user:{user_id}", user.to_json(), ttl=300)

        return user

Production

Production

service = UserService( repository=PostgresUserRepository(db), cache=RedisCache(redis), logger=StructlogLogger(), )
service = UserService( repository=PostgresUserRepository(db), cache=RedisCache(redis), logger=StructlogLogger(), )

Testing

Testing

service = UserService( repository=InMemoryUserRepository(), cache=FakeCache(), logger=NullLogger(), )
undefined
service = UserService( repository=InMemoryUserRepository(), cache=FakeCache(), logger=NullLogger(), )
undefined

Pattern 8: Avoiding Common Anti-Patterns

模式8:避免常见反模式

Don't expose internal types:
python
undefined
不要暴露内部类型:
python
undefined

BAD: Leaking ORM model to API

BAD: Leaking ORM model to API

@app.get("/users/{id}") def get_user(id: str) -> UserModel: # SQLAlchemy model return db.query(UserModel).get(id)
@app.get("/users/{id}") def get_user(id: str) -> UserModel: # SQLAlchemy model return db.query(UserModel).get(id)

GOOD: Use response schemas

GOOD: Use response schemas

@app.get("/users/{id}") def get_user(id: str) -> UserResponse: user = db.query(UserModel).get(id) return UserResponse.from_orm(user)

**Don't mix I/O with business logic:**

```python
@app.get("/users/{id}") def get_user(id: str) -> UserResponse: user = db.query(UserModel).get(id) return UserResponse.from_orm(user)

**不要将I/O与业务逻辑混合:**

```python

BAD: SQL embedded in business logic

BAD: SQL embedded in business logic

def calculate_discount(user_id: str) -> float: user = db.query("SELECT * FROM users WHERE id = ?", user_id) orders = db.query("SELECT * FROM orders WHERE user_id = ?", user_id) # Business logic mixed with data access
def calculate_discount(user_id: str) -> float: user = db.query("SELECT * FROM users WHERE id = ?", user_id) orders = db.query("SELECT * FROM orders WHERE user_id = ?", user_id) # Business logic mixed with data access

GOOD: Repository pattern

GOOD: Repository pattern

def calculate_discount(user: User, order_history: list[Order]) -> float: # Pure business logic, easily testable if len(order_history) > 10: return 0.15 return 0.0
undefined
def calculate_discount(user: User, order_history: list[Order]) -> float: # Pure business logic, easily testable if len(order_history) > 10: return 0.15 return 0.0
undefined

Best Practices Summary

最佳实践总结

  1. Keep it simple - Choose the simplest solution that works
  2. Single responsibility - Each unit has one reason to change
  3. Separate concerns - Distinct layers with clear purposes
  4. Compose, don't inherit - Combine objects for flexibility
  5. Rule of three - Wait before abstracting
  6. Keep functions small - 20-50 lines (varies by complexity), one purpose
  7. Inject dependencies - Constructor injection for testability
  8. Delete before abstracting - Remove dead code, then consider patterns
  9. Test each layer - Isolated tests for each concern
  10. Explicit over clever - Readable code beats elegant code
  1. 保持简单 - 选择可行的最简单解决方案
  2. 单一职责 - 每个单元只有一个修改的理由
  3. 分离关注点 - 职责明确的不同层级
  4. 组合而非继承 - 通过组合对象实现灵活性
  5. 三次原则 - 等待合适的时机再进行抽象
  6. 保持函数短小 - 20-50行(根据复杂度调整),单一职责
  7. 依赖注入 - 构造函数注入提升可测试性
  8. 先删除再抽象 - 移除无用代码后再考虑使用模式
  9. 分层测试 - 对每个关注点进行独立测试
  10. 显式优于巧妙 - 可读代码优于优雅代码