有勇气的牛排博客

Python单例模式介绍与实现

有勇气的牛排 525 Python 2024-09-21 22:11:21

1 前言

单例模式(Singleton Pattern) 是一种创建型设计模式,旨在确保一个类在程序运行期间只有一个实例,并且提供一个全局访问点。

单例模式的主要作用是控制实例的数量,防止类被实例化多次,以节省资源并确保全局状态的唯一性。

1.1 场景

  • 需要控制资源访问(数据库连接、文件句柄等)
  • 想要确保全局状态唯一性(日志记录、配置文件管理等)

1.2 单利模式关键点

  1. 在程序的整个生命周期中只会创建一次对象实例
  2. 必须提供一个全局访问点,以便其他代码可以访问唯一实例

1.3 常见方案有点对比

  • __new__ 方法适用于较为简单的单例实现。
  • 装饰器方式使得代码更加优雅且易于复用。
  • 模块方式利用了 Python 模块的特性,非常简洁。
  • 元类方式适合需要更高控制权的情况。

2 __new__方法

Python的 __new__方法是在实例化之前调用的,用于控制实例的常见,我们可以通过 __new__方法控制实例的创建,确保只创建一个实例

2.1 案例(唯一实例)

class SingleTest: _instance = None # 类属性,确保唯一的实例 def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) # 调用父类__new__方法创建实例 return cls._instance def __init__(self, name): self.name = name # 实例化 single_test1 = SingleTest("有勇气的牛排") single_test2 = SingleTest("个人博客") print(single_test1 is single_test2) # 输出:True,说明2个实例为同一个 print(single_test2.name) # 输出:个人博客,说明第二次初始化覆盖了第一次

Python使用__new__实现单例模式

2.2 场景

适合在需要严格控制类的实例化过程的场景中使用。比如创建一些资源密集的对象时。如:

  • 数据库连接
  • 缓存管理
  • 线程池等

数据库连接

class DBConnectionPool: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) cls._initialize_pool(cls._instance) return cls._instance @staticmethod def _initialize_pool(instance): # 假设初始化数据库连接池 instance.pool = "连接池初始化" # 测试 db1 = DBConnectionPool() db2 = DBConnectionPool() print(db1 is db2) # True print(db1.pool) # "连接池初始化"

3 装饰器

可以使用一个装饰器来将任何类转换为单例。

装饰器通过一个闭包来保存类的唯一实例。

无论调用多少次,只要该类已经被实例化,都会返回第一次创建的实例

3.1 案例

def singletest(cls): instance = {} def get_instance(*args, **kwargs): if cls not in instance: # 首次调用 创建实例 instance[cls] = cls(*args, **kwargs) return instance[cls] return get_instance @singletest class SingleTest: def __init__(self, name): self.name = name singletest1 = SingleTest("有勇气的牛排") singletest2 = SingleTest("个人博客") print(singletest1 is singletest2) # 输出True:说明2个为同一个实例 print(singletest1.name) print(singletest2.name) # 输出:有勇气的牛排,说明第一次实例化的值不会被第二次改变

image.png

3.2 场景

这种方法适合那些希望不修改现有类的情况下,将单例模式添加到类中的场景。例如,你可以为多个类应用相同的单例装饰器,而无需改变它们的定义。这种方式更灵活,可以应用到多种类中。

3.2.1 日志记录

日志记录器在应用程序中始终只有一个实例,但又不希望改变类的定义。

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 Logger: def __init__(self, log_file): self.log_file = log_file def log(self, message): print(f"Logging message: {message} to {self.log_file}") # 测试 logger1 = Logger("app.log") logger2 = Logger("error.log") print(logger1 is logger2) # True logger1.log("输出:有勇气的牛排")

3.3 升级多线程安全守护

线程锁:使用 threading.Lock() 来确保实例创建时的互斥性,避免多线程下同时创建多个实例。
通用性:这个改进后的版本可以安全用于数据库、多线程和日志等场景,尤其是在需要单例的多线程程序中。

场景:可以安全用于数据库、多线程和日志等场景,尤其是在需要单例的多线程程序中。

import threading def singleton(cls): instances = {} lock = threading.Lock() def get_instance(*args, **kwargs): with lock: if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return get_instance

4 模块化实现

Python 模块本质上是单例的,因为模块在第一次被导入时会执行其代码,后续的导入会直接引用第一次导入的结果。

4.1 案例

single_test.py

class SingleTest: def __init__(self): self.name = None single_test = SingleTest()

main.py

from single_test import single_test from single_test import single_test as single_test2 single_test.name = "有勇气的牛排" print(single_test.name) print(single_test2.name)

image.png

4.2 场景

4.2.1 配置管理或全局状态管理

模块级别的单例模式非常适合处理全局状态管理,尤其是当某个模块负责配置管理或者其他全局资源时。这种方式无需显式创建单例对象,模块本身天然是单例的。

在应用程序的各个部分,可能需要访问同一个全局配置文件,模块级单例是非常合适的。

config.py

class AppConfig: def __init__(self): self.name = "有勇气的牛排" config = AppConfig()

main.py

from config import config print(config.name) # 输出:有勇气的牛排 config.name = "个人博客" # 其他模块中继续使用同一配置 from config import config print(config.name) # 输出:个人博客

5 元类Metaclass

元类控制类的创建,我们可以通过自定义元类来控制类的实例化过程,实现单例模式。

5.1 案例

原文:https://www.couragesteak.com/article/480

class SingleTestMeta(type): _instance = {} # 存放单例实例 def __call__(cls, *args, **kwargs): if cls not in cls._instance: # 调用 父类 __call__ 创建实例 cls._instance[cls] = super().__call__(*args, **kwargs) return cls._instance[cls] class SingleTest(metaclass=SingleTestMeta): def __init__(self, name): self.name = name single_test1 = SingleTest("有勇气的牛排") single_test2 = SingleTest("个人博客") print(single_test1 is single_test2) # True: 为同一个实例 print(single_test1.name) print(single_test2.name)

image.png


留言

专栏
文章
加入群聊