设计模式与Pythonic:Python中设计模式的特殊实现方式

说实话,我刚开始用Python写设计模式时,总觉得有点别扭。Java里那一套类结构搬过来,代码又臭又长。后来我才明白——Python有自己的哲学,设计模式也得入乡随俗。

今天我们就聊聊,怎么用Python特有的语法糖,把单例、工厂、观察者这些模式写得既地道又优雅。

为什么设计模式在Python里要「变味」?

先想一个问题:设计模式本质上是解决什么?

说白了,是弥补语言的不足。Java缺闭包,所以用策略模式;C++缺垃圾回收,所以用RAII。Python呢?它天生就有装饰器、上下文管理器、元类、描述符这些高级特性。

所以,Python里的设计模式,不是照搬,而是「翻译」。用Pythonic的方式,把模式的意图表达出来。

核心观点:设计模式是「思想」,不是「模板代码」。Python的特性让我们能用更少的代码,实现更强的功能。

用装饰器实现单例模式

单例模式,最常见的写法是__new__或者元类。但我个人习惯用装饰器——干净、直观、可复用。

def singleton(cls):
    _instances = {}
    def get_instance(*args, **kwargs):
        if cls not in _instances:
            _instances[cls] = cls(*args, **kwargs)
        return _instances[cls]
    return get_instance

@singleton
class Database:
    def __init__(self):
        self.connection = None
        print("数据库连接已创建")

# 测试
db1 = Database()
db2 = Database()
print(db1 is db2)  # True

你看,一个装饰器搞定。不需要写__new__,不需要继承,甚至不需要改类本身的代码。

我在项目中遇到过一个问题:多线程环境下,这个写法不安全。两个线程同时调用get_instance,可能创建两个实例。怎么解决?加锁。

import threading

def singleton(cls):
    _instances = {}
    _lock = threading.Lock()
    def get_instance(*args, **kwargs):
        if cls not in _instances:
            with _lock:
                if cls not in _instances:  # 双重检查
                    _instances[cls] = cls(*args, **kwargs)
        return _instances[cls]
    return get_instance

小技巧:如果你用的是Python 3.12+,可以用@functools.cache配合类方法实现更简洁的单例。不过要注意,缓存的是返回值,不是实例本身。

用上下文管理器实现资源管理(类似RAII)

C++的RAII(资源获取即初始化)很强大。Python里谁来实现?上下文管理器

我经常用上下文管理器来封装「获取-使用-释放」这种模式。比如数据库连接、文件锁、临时环境变量。

import contextlib

@contextlib.contextmanager
def db_session(db_url):
    session = create_session(db_url)
    try:
        yield session
        session.commit()
    except Exception:
        session.rollback()
        raise
    finally:
        session.close()

# 使用
with db_session("sqlite:///test.db") as session:
    session.query(User).all()

这其实就是模板方法模式的Pythonic实现。你想想看,传统写法要写一个抽象类,定义open()close()execute(),子类去实现。现在一个@contextmanager搞定。

我曾经踩过的坑:上下文管理器里如果yield了多个值,或者yield在循环里,行为会变得很诡异。记住:yield只能出现一次,且只能yield一个值。

用描述符实现属性验证(类似代理模式)

代理模式,通常用来控制对对象的访问。Python里,描述符就是天然的代理。

class PositiveNumber:
    def __set_name__(self, owner, name):
        self._name = name

    def __get__(self, obj, objtype=None):
        return obj.__dict__.get(self._name, 0)

    def __set__(self, obj, value):
        if value <= 0:
            raise ValueError(f"{self._name} 必须为正数")
        obj.__dict__[self._name] = value

class Order:
    quantity = PositiveNumber()
    price = PositiveNumber()

    def __init__(self, qty, price):
        self.quantity = qty
        self.price = price

# 测试
order = Order(10, 99.9)
order.quantity = -5  # 抛出 ValueError

你看,PositiveNumber就是一个代理。它拦截了对quantityprice的赋值操作,做了验证。传统代理模式要写Proxy类,实现和原类一样的接口。Python里一个描述符搞定。

用生成器实现迭代器模式

迭代器模式,Python里最自然的方式就是生成器。没有之一。

def fibonacci(limit):
    a, b = 0, 1
    while a < limit:
        yield a
        a, b = b, a + b

for num in fibonacci(100):
    print(num)

生成器自动实现了__iter____next__。你不需要写Iterator类,不需要维护状态变量。yield一句,状态自动保存。

我记得有一次,我需要遍历一个巨大的日志文件(几十GB)。用列表一次性加载?内存直接爆掉。用生成器逐行读取?优雅又高效。

def read_large_file(file_path):
    with open(file_path, 'r') as f:
        for line in f:
            yield line.strip()

关键点:生成器是「惰性求值」的。它只在需要时才计算下一个值。这对处理大数据流、无限序列特别有用。

用魔术方法实现策略模式

策略模式,传统写法是定义接口、实现多个策略类、用上下文去调用。Python里,函数就是一等公民,策略可以直接传函数。

def quick_sort(data):
    print("快速排序")
    return sorted(data)

def merge_sort(data):
    print("归并排序")
    return sorted(data)

class Sorter:
    def __init__(self, strategy):
        self.strategy = strategy

    def sort(self, data):
        return self.strategy(data)

# 使用
sorter = Sorter(quick_sort)
sorter.sort([3, 1, 2])

更Pythonic的写法?直接用__call__让类实例可调用。

class QuickSort:
    def __call__(self, data):
        print("快速排序")
        return sorted(data)

class MergeSort:
    def __call__(self, data):
        print("归并排序")
        return sorted(data)

sorter = Sorter(QuickSort())
sorter.sort([3, 1, 2])

哪种好?看场景。如果策略很简单,函数就够了。如果策略需要维护状态(比如缓存、计数器),用类更合适。

用弱引用实现观察者模式

观察者模式,Python里最头疼的问题是内存泄漏。观察者注册到主题后,如果观察者被销毁了,但主题还持有它的引用,就泄漏了。

解决方案:用weakref

import weakref

class Observable:
    def __init__(self):
        self._observers = weakref.WeakSet()

    def register(self, observer):
        self._observers.add(observer)

    def unregister(self, observer):
        self._observers.discard(observer)

    def notify(self, event):
        for observer in self._observers:
            observer.update(event)

class Observer:
    def __init__(self, name):
        self.name = name

    def update(self, event):
        print(f"{self.name} 收到事件: {event}")

# 使用
obs = Observable()
o1 = Observer("观察者1")
obs.register(o1)
obs.notify("数据更新")

del o1  # 观察者被销毁
obs.notify("再次更新")  # 不会报错,也不会泄漏

WeakSet里的元素,如果没有强引用,会自动被移除。你不需要手动unregister,省心多了。

我建议:如果你的观察者模式涉及大量事件通知,考虑用asyncioSignal或者第三方库blinker。它们性能更好,功能更全。

一张图总结:Pythonic设计模式的核心逻辑

Pythonic设计模式核心逻辑 Python 语言特性 装饰器 单例模式、缓存、日志 上下文管理器 RAII、模板方法、资源管理 生成器 迭代器模式、惰性求值 描述符 代理模式、属性验证 弱引用 观察者模式、事件系统 用更少的代码,表达更强的意图

总结:Pythonic设计模式的三个原则

  1. 优先用语言特性,而不是类结构。装饰器、上下文管理器、生成器,这些是Python的「原生武器」。
  2. 关注意图,而不是形式。单例模式的核心是「唯一实例」,不是__new__方法。用装饰器实现,更干净。
  3. 考虑内存和性能。Python的自动内存管理不是万能的。观察者模式用弱引用,生成器用惰性求值,这些都是实战中积累的经验。

嗯,说到底,设计模式是工具,不是教条。Python给了我们更灵活的工具箱,就看你怎么用了。

我的建议:下次你在Python里实现设计模式时,先问自己一句——「Python有没有更简单的方式?」如果有,就用它。如果没有,再考虑传统的类结构。

公众号:蓝海资料掘金营,微信deep3321