本文汇总介绍几个Python语言下,稍微不是那么基础的概念(有个人主观倾向在里面)。

单例模式

参考设计模式之单例模式(Java)

在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
  • 支持lambdaOrder(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,

三个必要条件

  • 嵌套函数:函数内部定义另一个函数;
  • 内层函数引用外层变量:不是参数,也不是全局变量,而是外层函数的局部变量;
  • 外层函数返回内层函数(或让内层函数在外部被调用)。

解决问题:

  • 封装私有状态:无需类,就能创建带私有变量的对象;
  1. 避免全局变量污染:把状态“打包”进函数,而不是放在全局作用域;
  2. 实现装饰器的基础:装饰器本质上就是利用闭包来增强函数功能;
  3. 回调函数携带上下文:在事件驱动编程中,让回调函数记住创建时的环境。

柯里化

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 / cytoolz
    • compose:函数组合
    • pipe:管道操作
    • curry:柯里化
    • memoize:记忆化
  • fn.py
    • 函数式编程工具集合
    • 柯里化、函数组合等
  • returns
    • 函数式编程容器
    • Maybe、Result、IO等单子

最佳实践:

  • 优先使用标准库:functools提供大多数常用功能
  • 保持函数纯净:避免副作用,便于组合和测试
  • 使用类型提示:复杂的高阶函数需要清晰的类型注解
  • 适当抽象:不要过度使用高阶函数,保持代码可读性

注意事项:

  • 调试困难:调用栈可能变得复杂
  • 性能开销:多层闭包和函数调用可能影响性能
  • 可读性:对不熟悉函数式编程的开发者可能难以理解
  • 错误处理:需要统一的错误处理策略

这些概念共同构成强大的函数变换工具箱:

  • 偏函数/部分应用:参数预设和函数定制
  • 柯里化:函数标准化和延迟求值
  • 函数组合:构建复杂处理管道
  • 记忆化:性能优化关键技术
  • 装饰器:横切关注点分离

更多推荐