设计模式与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就是一个代理。它拦截了对quantity和price的赋值操作,做了验证。传统代理模式要写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,省心多了。
我建议:如果你的观察者模式涉及大量事件通知,考虑用asyncio的Signal或者第三方库blinker。它们性能更好,功能更全。
一张图总结:Pythonic设计模式的核心逻辑
总结:Pythonic设计模式的三个原则
- 优先用语言特性,而不是类结构。装饰器、上下文管理器、生成器,这些是Python的「原生武器」。
- 关注意图,而不是形式。单例模式的核心是「唯一实例」,不是
__new__方法。用装饰器实现,更干净。 - 考虑内存和性能。Python的自动内存管理不是万能的。观察者模式用弱引用,生成器用惰性求值,这些都是实战中积累的经验。
嗯,说到底,设计模式是工具,不是教条。Python给了我们更灵活的工具箱,就看你怎么用了。
我的建议:下次你在Python里实现设计模式时,先问自己一句——「Python有没有更简单的方式?」如果有,就用它。如果没有,再考虑传统的类结构。
公众号:蓝海资料掘金营,微信deep3321