Python入门之高级概念:单例模式、策略模式、函数(装饰器、闭包、偏函数、柯里化、函数提升/组合/绑定、记忆化)
本文汇总介绍几个Python语言下,稍微不是那么基础的概念(有个人主观倾向在里面)。
单例模式
在Python语境下,同样意思:确保一个类在全局范围内只有一个实例。
适用场景
- 配置管理器:全局共享配置,避免重复加载文件
- 数据库连接池:减少连接创建开销
- 日志记录器:统一日志输出入口
实现方案有很多。
装饰器
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
@singleton
class ConfigManager:
def __init__(self):
self.settings = {"version": "1.0", "debug": False}
# 测试
c1 = ConfigManager()
c2 = ConfigManager()
print(c1 is c2) # True
实现原理:装饰器在类定义时执行,将原类替换为一个闭包函数get_instance,该函数内部维护一个字典instances存储已创建的实例。首次调用时创建实例并缓存,后续调用直接返回缓存对象。结合threading.Lock()可保证多线程安全。
模块导入法
Python模块在首次导入时会被执行并缓存到sys.modules中,后续导入直接返回已缓存的模块对象。可将单例实例定义在模块级别。
# singleton_module.py
class _SingletonClass:
def __init__(self):
self.value = "I'm a singleton"
def do_something(self):
return f"Working with {self.value}"
# 在模块级别创建唯一实例
singleton_instance = _SingletonClass()
# 使用
# from singleton_module import singleton_instance
原理:
- Python的导入系统是天然的单例管理器
- 模块加载时执行顶层代码,创建实例
sys.modules字典确保模块只加载一次
优点:
- 极其简单,无需额外代码
- 线程安全,模块导入在Python中由GIL保护
- 符合Python哲学:简单胜于复杂
缺点:
- 缺乏延迟初始化,导入即创建
- 难以传递初始化参数
- 类定义和实例耦合在模块中
元类法
元类,Metaclass,是类的类,控制类的创建行为。通过自定义元类,在类被创建时拦截实例化过程。
import threading
class SingletonMeta(type):
"""单例元类"""
_instances = {}
_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
# 双重检查锁定模式
if cls not in cls._instances:
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class DatabaseConnection(metaclass=SingletonMeta):
def __init__(self, connection_string):
self.connection_string = connection_string
self._connection = None
print(f"Database连接初始化: {connection_string}")
def connect(self):
if not self._connection:
self._connection = f"连接到 {self.connection_string}"
return self._connection
# 使用
db1 = DatabaseConnection("mysql://localhost:3306/mydb")
db2 = DatabaseConnection("postgres://localhost:5432/mydb") # 不会重新初始化
print(db1 is db2) # True
print(db1.connection_string) # 仍然是 "mysql://localhost:3306/mydb"
原理:
- 元类的
__call__方法在类实例化时被调用 - 使用类字典
_instances缓存实例 - 通过检查是否已存在实例来控制创建
优点:
- 真正的面向对象,继承关系清晰
- 支持延迟初始化(首次调用时创建)
- 可以传递初始化参数(但只生效一次)
- 类定义干净,没有额外装饰器
缺点:
- 元类概念较复杂,对新手不友好
- 多重继承时可能冲突(元类冲突)
- 需要小心处理线程安全(基础版本非线程安全)
类属性法
Borg模式/Monostate模式,不限制实例数量,但确保所有实例共享相同的状态。通过让所有实例的__dict__指向同一个字典来实现。
class Borg:
"""Borg模式实现单例状态共享"""
_shared_state = {}
def __init__(self):
self.__dict__ = self._shared_state
# 可以在这里添加初始化逻辑,但注意不要覆盖共享状态
class Configuration(Borg):
def __init__(self, config=None):
super().__init__()
if config and not hasattr(self, 'initialized'):
self.config_data = config
self.initialized = True
def get(self, key, default=None):
return self.config_data.get(key, default)
# 使用
config1 = Configuration({"host": "localhost", "port": 8080})
config2 = Configuration({"host": "example.com"}) # 这个配置不会生效
print(config1.get("host")) # "localhost"
print(config2.get("host")) # "localhost" (共享状态)
print(config1 is config2) # False (不同实例)
print(config1.__dict__ is config2.__dict__) # True (共享字典)
原理:
- Python中实例属性存储在
__dict__中 - 通过让所有实例的
__dict__指向同一个字典对象实现状态共享 _shared_state类变量是所有实例共享的命名空间
优点:
- 允许多个"实例"对象,更灵活
- 状态自动同步,所有实例看到相同数据
- 继承友好,子类自动获得单例行为
- 没有实例创建限制,更符合Python动态特性
缺点:
- 不是真正的单例(可创建多个实例对象)
- 初始化逻辑需要特殊处理(检查
initialized标志) __dict__重定向可能影响某些特殊方法
__new__方法
在类的__new__方法(负责创建实例)中控制实例化逻辑,在__init__(负责初始化)之前执行。
class SingletonClass:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name):
# 注意:__init__每次实例化都会被调用
if not hasattr(self, '_initialized'):
self.name = name
self._initialized = True
print(f"初始化Singleton: {name}")
def __str__(self):
return f"Singleton(name={self.name})"
# 使用
s1 = SingletonClass("第一个实例")
s2 = SingletonClass("第二个实例") # 会打印"初始化Singleton",但name不会改变
print(s1) # Singleton(name=第一个实例)
print(s2) # Singleton(name=第一个实例)
print(s1 is s2) # True
原理:
__new__是静态方法,创建并返回实例__init__是实例方法,初始化已创建的实例- 通过类属性
_instance缓存首次创建的实例
优点:
- 控制点在实例创建过程,逻辑清晰
- 兼容性好,不需要元类或装饰器
- 可以与其他特殊方法配合
缺点:
__init__可能被多次调用,需要防护逻辑- 线程不安全(需要额外同步)
- 继承时需要小心重写
__new__
闭包和工厂函数
利用闭包的封装特性,将实例隐藏在函数作用域内,通过工厂函数控制访问。
def make_singleton(cls):
"""使用闭包实现的单例工厂"""
instance = None
def get_instance(*args, **kwargs):
nonlocal instance
if instance is None:
instance = cls(*args, **kwargs)
return instance
return get_instance
# 定义普通类
class Logger:
def __init__(self, log_level="INFO"):
self.log_level = log_level
self.logs = []
def log(self, message):
entry = f"[{self.log_level}] {message}"
self.logs.append(entry)
print(entry)
def get_logs(self):
return self.logs
# 创建单例工厂
get_logger = make_singleton(Logger)
# 使用
logger1 = get_logger("DEBUG")
logger2 = get_logger("ERROR") # 参数不会生效
logger1.log("第一条日志")
logger2.log("第二条日志")
print(logger1 is logger2) # True
print(logger1.get_logs()) # 包含两条日志
原理:
- 闭包捕获并封装
instance变量 - 工厂函数
get_instance是访问实例的唯一入口 nonlocal关键字允许修改外部作用域变量
优点:
- 完全封装,实例完全私有
- 灵活的初始化控制
- 函数式风格,与装饰器类似但更显式
缺点:
- 破坏类直接实例化的语义
- 类型提示和IDE支持可能受影响
- 需要额外工厂函数
标准库
利用lru_cache缓存机制,缓存类实例化函数的结果。
from functools import lru_cache
class ApiClient:
def __init__(self, api_key):
self.api_key = api_key
self.session = None
print(f"创建ApiClient,API Key: {api_key[:8]}...")
def connect(self):
if not self.session:
self.session = f"Session for {self.api_key[:8]}..."
return self.session
# 使用lru_cache缓存实例
@lru_cache(maxsize=1)
def get_api_client(api_key):
return ApiClient(api_key)
# 使用
client1 = get_api_client("sk-1234567890abcdef")
client2 = get_api_client("sk-1234567890abcdef") # 从缓存返回
client3 = get_api_client("sk-0987654321fedcba") # 新实例,不同参数
print(client1 is client2) # True
print(client1 is client3) # False
原理:
lru_cache为函数调用创建缓存maxsize=1确保只缓存最近的一个结果- 函数参数作为缓存键,相同参数返回缓存结果
优点:
- 使用标准库,无需自定义代码
- 支持参数化单例(不同参数不同实例)
- 线程安全,
lru_cache内部有锁 - 自带缓存管理(可设置大小、统计等)
缺点:
- 不是真正的类单例,而是函数缓存
- 需要额外函数包装
- 缓存键基于参数哈希,可能不是所有类型都可用
对比
| 方案 | 实现难度 | 线程安全 | 延迟初始化 | 支持继承 | 参数处理 | Pythonic程度 |
|---|---|---|---|---|---|---|
| 装饰器 | ⭐☆☆☆☆ | ✅ | ✅ | ⚠️ | ❌ | ⭐⭐⭐⭐⭐ |
| 模块导入法 | ⭐☆☆☆☆ | ✅ | ❌ | ⚠️ | ❌ | ⭐⭐⭐⭐⭐ |
| 元类法 | ⭐⭐⭐⭐☆ | ⚠️(需加锁) | ✅ | ⚠️ | ✅(首次) | ⭐⭐⭐☆☆ |
| Borg模式 | ⭐⭐⭐☆☆ | ⚠️ | ✅ | ✅ | ⚠️ | ⭐⭐⭐⭐☆ |
__new__法 |
⭐⭐⭐☆☆ | ⚠️(需加锁) | ✅ | ⚠️ | ✅(首次) | ⭐⭐⭐☆☆ |
| 闭包工厂 | ⭐⭐☆☆☆ | ⚠️ | ✅ | ❌ | ✅ | ⭐⭐⭐☆☆ |
lru_cache |
⭐☆☆☆☆ | ✅ | ✅ | ❌ | ✅ | ⭐⭐⭐⭐☆ |
最佳实践原则:
- 优先考虑模块导入法:如果适用,这是最Pythonic的选择
- 需要继承时考虑Borg模式:提供更大的灵活性
- 需要精细控制时使用元类:适合框架和库开发
- 避免过度设计:单例有时是反模式,考虑是否真的需要
- 注意测试难度:单例可能增加单元测试的复杂性
在Python中,单例模式有时被视为"反模式"或"代码异味":
- 模块系统本身提供单例:通过模块级别的变量
- 依赖注入更灵活:使用工厂函数或依赖注入容器
- 全局状态应谨慎:单例本质上是全局状态,可能带来耦合和测试问题
更Pythonic的替代方案:
- 使用模块变量代替类单例
- 使用依赖注入传递共享实例
- 使用上下文管理器管理资源生命周期
- 使用函数闭包封装状态
策略模式
策略模式的核心思想是:将算法/行为封装成独立的策略,使它们可以互相替换,而不影响使用它们的上下文。
函数
函数是一等对象
# 定义策略函数
def normal_discount(price):
return price * 0.95
def vip_discount(price):
return price * 0.8
class Order:
def __init__(self, price, discount_strategy):
self.price = price
self.discount_strategy = discount_strategy
def final_price(self):
return self.discount_strategy(self.price)
order1 = Order(100, normal_discount)
优点:
- 简洁、直观、无需定义多余类
- 策略可动态替换:
order.discount_strategy = vip_discount - 支持
lambda:Order(100, lambda p: p * 0.9)
OOP
用类实现:
from abc import ABC, abstractmethod
# 抽象策略接口
class DiscountStrategy(ABC):
@abstractmethod
def calculate(self, price: float) -> float:
pass
# 具体策略
class NormalDiscount(DiscountStrategy):
def calculate(self, price):
return price * 0.95
class VipDiscount(DiscountStrategy):
def calculate(self, price):
return price * 0.8
class Order:
def __init__(self, price, strategy: DiscountStrategy):
self.price = price
self.strategy = strategy
def final_price(self):
return self.strategy.calculate(self.price)
order = Order(100, VipDiscount())
print(order.final_price())
适用场景:
- 策略本身有状态,如缓存、配置
- 策略逻辑复杂,需多个方法
- 团队习惯强类型/OOP
字典映射
将策略注册到字典,通过Key动态选择
# 策略函数
def normal(price): return price * 0.95
def vip(price): return price * 0.8
# 策略注册表
DISCOUNT_STRATEGIES = {
'normal': normal,
'vip': vip
}
class Order:
def __init__(self, price, strategy_name):
self.price = price
self.strategy = DISCOUNT_STRATEGIES[strategy_name]
def final_price(self):
return self.strategy(self.price)
# 常用于从配置/用户输入选择策略
order = Order(100, 'vip')
print(order.final_price())
优点:
- 易于扩展:加新策略只需往字典加一项
- 适合从外部,如JSON配置、API参数指定策略
装饰器
自动收集策略,避免手动维护字典
STRATEGIES = {}
def register_strategy(name):
def decorator(func):
STRATEGIES[name] = func
return func
return decorator
@register_strategy('normal')
def normal_discount(price):
return price * 0.95
@register_strategy('vip')
def vip_discount(price):
return price * 0.8
class Order:
def __init__(self, price, strategy_name):
self.price = price
self.strategy = STRATEGIES[strategy_name]
def final_price(self):
return self.strategy(self.price)
order = Order(100, 'vip')
print(order.final_price())
优点:
- 策略自动注册,解耦
- 模块化强,适合插件系统
工厂模式+闭包/偏函数
from functools import partial
class DiscountStrategy:
def __init__(self):
self.strategies = {}
def register(self, name, func, *args, **kwargs):
"""注册策略(使用偏函数预设参数)"""
if args or kwargs:
self.strategies[name] = partial(func, *args, **kwargs)
else:
self.strategies[name] = func # 直接存储原函数,避免无谓包装
def apply(self, name, *args, **kwargs):
"""执行策略"""
if name not in self.strategies:
raise ValueError(f"未知策略: {name}")
return self.strategies[name](*args, **kwargs)
def percentage_discount(price, rate):
return price * rate
discounts = DiscountStrategy()
discounts.register('normal', lambda p: p * 0.95)
discounts.register('vip', lambda p: p * 0.8)
discount = DiscountStrategy(100, 'vip')
print(discount.final_price())
对比
| 方法 | 代码量 | 灵活性 | 适用场景 |
|---|---|---|---|
| 函数 | ⭐最少 | ⭐⭐⭐最高 | 大多数情况(推荐首选) |
| OOP类 | 较多 | 中等 | 策略有状态/复杂逻辑 |
| 字典映射 | 中等 | 高 | 配置驱动、动态选择 |
| 装饰器注册 | 中等 | ⭐⭐⭐高 | 插件系统、自动发现 |
| 工厂+偏函数 | 中等 | ⭐⭐⭐高 | 插件系统、自动发现 |
函数
关于函数式编程,参考编程范式:函数式编程&防御式编程&响应式编程&契约式编程&流式编程。
下面汇总介绍几个重要概念。
装饰器
Decorators,一种高阶函数,用于动态修改函数/类的行为,而不改变其源码。可修改其他函数的功能的函数,有助于让代码更简短,也更Pythonic(Python范儿)。
也是语法糖:@decorator等价于func = decorator(func)。
装饰器方式可优雅地将任意类转换为单例,且调用方式与普通类一致。
优势:
- 横切关注点(Cross-cutting Concerns):如日志、权限检查、缓存、计时等,避免重复代码;
- 功能增强:在不侵入原逻辑的前提下扩展能力。
示例:
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.time()-start:.2f}s")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
偏函数
Partial Function,一种特殊函数,通过固定原函数部分参数,创建出新函数,其创建主要通过 functools.partial 实现:
from functools import partial
def add(a, b):
return a + b
# 创建偏函数,固定a=10
add_ten = partial(add, 10)
# 调用偏函数
print(add_ten(5)) # 输出15
print(add_ten(10)) # 输出20
好处:无需重复传递某些固定参数,减少函数调用时需要提供的参数数量。
闭包
Closure,
三个必要条件
- 嵌套函数:函数内部定义另一个函数;
- 内层函数引用外层变量:不是参数,也不是全局变量,而是外层函数的局部变量;
- 外层函数返回内层函数(或让内层函数在外部被调用)。
解决问题:
- 封装私有状态:无需类,就能创建带私有变量的对象;
- 避免全局变量污染:把状态“打包”进函数,而不是放在全局作用域;
- 实现装饰器的基础:装饰器本质上就是利用闭包来增强函数功能;
- 回调函数携带上下文:在事件驱动编程中,让回调函数记住创建时的环境。
柯里化
Currying,将接受多个参数的函数转换为一系列接受单个参数的函数的过程。
对比:
- 偏函数:固定部分参数,返回接受剩余参数的新函数
- 柯里化:将多参数函数转换为嵌套的单参数函数链
示例:
def add(x, y, z):
return x + y + z
# 柯里化版本
def curried_add(x):
def inner1(y):
def inner2(z):
return x + y + z
return inner2
return inner1
result = curried_add(1)(2)(3) # 返回6
add_1 = curried_add(1) # 返回函数 inner1
add_1_2 = add_1(2) # 返回函数 inner2
add_1_2(3) # 返回6
# 使用functools.partial模拟柯里化
from functools import partial
curried_add_v2 = lambda x: lambda y: lambda z: x + y + z
原理:
- 基于λ演算的理论基础
- 通过函数闭包实现参数的部分应用
- 每个函数只接受一个参数,返回接受下一个参数的函数
应用场景:
- 函数组合和管道操作
- 延迟计算和惰性求值
- 创建可配置的函数工厂
函数组合
Function Composition,将多个函数组合成一个新函数,前一个函数的输出作为后一个函数的输入。
示例:
def compose(*funcs):
"""从右向左组合函数"""
def composed(x):
result = x
for f in reversed(funcs):
result = f(result)
return result
return composed
# 工具函数
def add_one(x): return x + 1
def multiply_by_two(x): return x * 2
def square(x): return x ** 2
# 组合:square(multiply_by_two(add_one(x)))
transform = compose(square, multiply_by_two, add_one)
result = transform(3) # 64
# 使用operator模块
from operator import add, mul
from functools import partial
composed_func = compose(lambda x: x**2, partial(mul, 2), partial(add, 1))
原理:
- 函数作为一等公民,可以作为参数和返回值
- 通过闭包捕获函数序列
- 从右向左或从左向右执行顺序
应用场景:
- 数据处理管道
- 中间件链(如Express.js、Koa)
- 转换器模式
函数绑定
Function Binding,将函数与特定的上下文(self值)绑定,创建新函数。
class Calculator:
def __init__(self, base):
self.base = base
def add(self, x):
return self.base + x
# 方法绑定
calc = Calculator(10)
bound_method = calc.add # 自动绑定self
print(bound_method(5)) # 15
# 使用types.MethodType手动绑定
import types
unbound_func = Calculator.add
calc_instance = Calculator(10)
bound_func = types.MethodType(unbound_func, calc_instance)
原理:
- 闭包捕获上下文环境
self参数的显式绑定- 创建与特定对象关联的函数
应用场景:
- 事件处理回调
- 面向对象的方法调用
- 异步编程中的上下文保持
记忆化
Memoization,缓存函数计算结果,避免重复计算相同输入。
from functools import lru_cache
# 使用装饰器
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
# 手动实现上述效果
def memoize(func):
cache = {}
def memoized(*args):
key = args
if key not in cache:
cache[key] = func(*args)
return cache[key]
return memoized
@memoize
def expensive_computation(x, y):
import time
time.sleep(1)
return x * y + x + y
原理:
- 使用字典缓存输入参数到计算结果的映射
- 闭包保持缓存状态
- 输入参数的哈希作为缓存键
应用场景:
- 动态规划算法
- 昂贵的计算或IO操作
- 递归函数优化
反柯里化
Uncurrying,柯里化的逆过程,将柯里化函数转换回多参数函数。
# 柯里化函数
curried_add = lambda x: lambda y: x + y
# 反柯里化
def uncurry(func):
def uncurried(*args):
result = func
for arg in args:
result = result(arg)
return result
return uncurried
# 使用
normal_add = uncurry(curried_add)
print(normal_add(3, 4)) # 7
函数提升
Lifting,将普通函数提升为操作在特定上下文(如Maybe、List、Future)中的函数。
def add(x, y):
return x + y
# 提升到列表上下文(应用每个组合)
def lift_to_list(func):
def lifted(list1, list2):
return [func(x, y) for x in list1 for y in list2]
return lifted
list_add = lift_to_list(add)
result = list_add([1, 2], [3, 4]) # [4, 5, 5, 6]
总结
汇总几个库:
functools:Python标准库,提供API:partial:偏函数lru_cache:记忆化reduce:归约操作wraps:装饰器工具
toolz/cytoolzcompose:函数组合pipe:管道操作curry:柯里化memoize:记忆化
fn.py- 函数式编程工具集合
- 柯里化、函数组合等
returns- 函数式编程容器
- Maybe、Result、IO等单子
最佳实践:
- 优先使用标准库:
functools提供大多数常用功能 - 保持函数纯净:避免副作用,便于组合和测试
- 使用类型提示:复杂的高阶函数需要清晰的类型注解
- 适当抽象:不要过度使用高阶函数,保持代码可读性
注意事项:
- 调试困难:调用栈可能变得复杂
- 性能开销:多层闭包和函数调用可能影响性能
- 可读性:对不熟悉函数式编程的开发者可能难以理解
- 错误处理:需要统一的错误处理策略
这些概念共同构成强大的函数变换工具箱:
- 偏函数/部分应用:参数预设和函数定制
- 柯里化:函数标准化和延迟求值
- 函数组合:构建复杂处理管道
- 记忆化:性能优化关键技术
- 装饰器:横切关注点分离
更多推荐



所有评论(0)