别再手写单例模式了,Python 的这个模块天然支持,一行搞定.
你肯定见过这样的代码,一个类被翻来覆去写了七八遍,就为了确保它只有一个实例。每次写单例模式,先定义类变量,再改__new__方法,或者搞个装饰器。我见过最夸张的同事,一个单例写了四十行代码,老老实实加锁、判断、双重检查锁。说实话,大部分场景根本不需要这么复杂。
Python 里有个模块叫 importlib,它天然支持单例。你不需要写任何特殊代码,因为 Python 的模块在第一次导入后就会被缓存到 sys.modules 里。同一个模块不管被导入多少次,永远只执行一次初始化代码。这就是最干净的单例模式。
举个例子,你创建一个文件叫 config.py,里面写:
class AppConfig:
def __init__(self):
self.name = '我的应用'
config = AppConfig()
然后在其他地方导入这个模块:
from config import config
不管你调多少次 import,config 永远是同一个对象。模块加载的瞬间对象就创建好了,你不需要操心多线程问题,不需要写锁。Python 解释器在导入模块时已经帮你处理好了线程安全。
有人会问,如果我想延迟初始化怎么办。很简单,用函数封装一下:
database.py
_db_instance = None
def get_db():
global _db_instance
if _db_instance is None:
_db_instance = DBCnnection()
return _db_instance
然后在别的文件里 from database import get_db。这比手写类单例清爽多了。
我见过不少新手用传统的方式写单例,把一个简单的配置类变得又长又难看。代码量翻了三倍,可读性降了一半。用模块天然的单例特性,代码短,逻辑直白,别人一看就懂。你写完这个,同事接你代码都不用皱眉。
再说个实际案例。你写一个日志记录器,通常全局只需要一个。以前我写过这样的:
import logging
def get_logger():
return logging.getLogger('my_app')
logger = get_logger()
把这个放在 logger.py 里,到处导入 logger 就行。logging 模块内部本来就有缓存机制,加上模块的单例,双重保险。你再也不用担心日志重复输出的问题。
有一种观点说,用模块做单例不够灵活,没办法方便地重置状态。这话没错,但大部分业务场景根本不需要重置。如果你非要重置,直接清一下 sys.modules 就行。不过别乱用,这是个脏操作,生产环境不推荐。
说真的,我看了不少开源项目,很多高质量的 Python 库都直接用模块变量做单例。比如 requests 库内部,session 对象就是模块级别创建的。人家几千星的项目都这么干,你何必自己手写一堆申明。
最后提醒一句,如果你用 __init__.py 里的变量做单例,记得小心循环导入。如果两个模块互相导入,Python 会报错。这个坑顶多浪费你十分钟,写法上注意单向依赖就行。