python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python单例模式

Python面向对象之单例模式的四大实现方法详解

作者:星河耀银海

本文将用通俗易懂的方式为大家详细介绍Python中单例模式的四种实现方法,包括__new__、装饰器、元类和模块并对比优缺点,通过日志管理器和数据库连接池实战案例,帮你彻底掌握单例模式,写出更高效的代码

一、开篇:只能有一个实例的类 

单例模式(Singleton)是最常用的设计模式之一:保证一个类只有一个实例,并提供全局访问点。典型的场景包括:数据库连接池、配置管理器、日志器、应用状态管理器。

先看一个直观的需求:

# 问题:全局配置应该只有一个实例
# 如果每个模块都创建新的Config对象,配置就会不一致
# ❌ 多个实例——配置不一致
# config1 = AppConfig()  # debug=True
# config2 = AppConfig()  # debug=False —— 这是另一个配置了!
# ✅ 单例——只有一个实例
# config1 = AppConfig()  # 创建实例
# config2 = AppConfig()  # 返回同一个实例
# config1 is config2     # True —— 同一个对象

二、Python实现单例的四种方式

2.1 使用__new__(最常用)

class Singleton:
    """单例——通过__new__控制实例创建"""
    _instance = None
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance
    def __init__(self, value=None):
        # ⚠️ __init__每次都会调用!需要防护
        if self._initialized:
            return
        self.value = value
        self._initialized = True
# 测试
s1 = Singleton("第一次")
s2 = Singleton("第二次")
print(s1 is s2)        # True —— 同一个实例
print(s1.value)        # 第一次 —— __init__只执行了一次
print(s2.value)        # 第一次

2.2 使用装饰器

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, connection_string="default"):
        self.connection_string = connection_string
        print(f"创建数据库连接: {connection_string}")
# 测试
db1 = Database("mysql://localhost/mydb")
db2 = Database("postgresql://localhost/testdb")
print(db1 is db2)                 # True
print(db1.connection_string)     # mysql://localhost/mydb
print(db2.connection_string)     # mysql://localhost/mydb
# ⚠️ 装饰器方式的缺点:
# Database现在是一个函数而不是类
print(type(Database))  # <class 'function'>
# isinstance检查不适用

2.3 使用元类(最彻底的实现)

class SingletonMeta(type):
    """单例元类——自动让类成为单例"""
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]
class Config(metaclass=SingletonMeta):
    """配置管理器——单例(通过元类)"""
    def __init__(self, debug=False):
        # 不需要_initialized防护——元类控制__init__只调用一次
        self.debug = debug
        self.settings = {}
        print(f"Config初始化: debug={debug}")
# 测试
c1 = Config(debug=True)
c2 = Config(debug=False)
print(c1 is c2)      # True
print(c1.debug)      # True —— 第一次初始化的值
print(c2.debug)      # True
# 💡 元类方式最优雅——类定义时就决定了单例行为
# __init__自动只执行一次

2.4 使用模块(Pythonic方式)

# 💡 Python的模块本身就是单例!
# 模块在第一次导入时执行,之后被缓存到sys.modules
# 这是最"Pythonic"的单例实现方式
# ===== config.py =====
"""
class AppConfig:
    def __init__(self):
        self.debug = False
        self.host = "localhost"
        self.port = 8080
    def set_debug(self, enabled):
        self.debug = enabled
# 在模块级别创建实例——这就是单例!
config = AppConfig()
"""
# ===== main.py =====
# from config import config
# 
# # 所有地方导入的都是同一个config对象
# config.set_debug(True)
# 
# # 另一个文件
# from config import config
# print(config.debug)  # True —— 同一个对象
# 💡 优点:简单、不需要任何特殊语法
# 💡 缺点:导入时即创建(不能延迟)、无法传参构造

三、单例的实战应用

3.1 日志管理器

import threading
from datetime import datetime
class Logger(metaclass=SingletonMeta):
    """日志管理器——全局唯一"""
    def __init__(self):
        self._lock = threading.Lock()
        self._handlers = []
        self._level = "INFO"
    def set_level(self, level):
        self._level = level
    def add_handler(self, handler):
        self._handlers.append(handler)
    def log(self, message, level="INFO"):
        """线程安全的日志记录"""
        with self._lock:
            timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
            log_entry = f"[{timestamp}] [{level}] {message}"
            # 输出到所有处理器
            for handler in self._handlers:
                handler(log_entry)
            # 默认输出到控制台
            if not self._handlers:
                print(log_entry)
# 使用——在任何地方获取同一个Logger
def some_function():
    logger = Logger()
    logger.log("函数执行中...")
def another_function():
    logger = Logger()
    logger.log("另一个函数执行中...", level="WARNING")
some_function()
another_function()

3.2 数据库连接池

from threading import Lock
class ConnectionPool(metaclass=SingletonMeta):
    """数据库连接池——全局唯一"""
    def __init__(self, max_connections=10):
        self.max_connections = max_connections
        self._connections = []
        self._in_use = set()
        self._lock = Lock()
        print(f"连接池创建: 最大{max_connections}个连接")
    def get_connection(self):
        """获取一个连接"""
        with self._lock:
            if len(self._connections) < self.max_connections:
                conn = f"连接-{len(self._connections) + 1}"
                self._connections.append(conn)
            else:
                raise RuntimeError("连接池已满")
            self._in_use.add(conn)
            return conn
    def release_connection(self, conn):
        """释放连接"""
        with self._lock:
            self._in_use.discard(conn)
    def status(self):
        return {
            "total": len(self._connections),
            "in_use": len(self._in_use),
            "available": len(self._connections) - len(self._in_use),
        }
# 使用
pool = ConnectionPool(max_connections=5)
conn1 = pool.get_connection()
conn2 = pool.get_connection()
print(pool.status())  # {'total': 2, 'in_use': 2, 'available': 0}
pool.release_connection(conn1)
print(pool.status())  # {'total': 2, 'in_use': 1, 'available': 1}

四、总结

单例模式保证"只有一个实例"。Python提供了多种实现——从简单的模块到彻底的元类。

四种实现对比:

方式优点缺点
__new__简单直观需要防__init__重复
装饰器简洁类型变成函数
元类最彻底稍复杂
模块最Pythonic不能延迟创建

日常推荐:模块级别实例(最简单)→ __new__方式(需要传参)→ 元类(框架/库)。

到此这篇关于Python面向对象之单例模式的四大实现方法详解的文章就介绍到这了,更多相关Python单例模式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文