Python 设计模式之单例模式


单例模式是一种创建型设计模式,其目的是为了保证一个类仅有一个实例,并提供一个访问它的全局访问点,这么做有时是为了限制对共享资源的并发访问。

关于这个模式,我不打算像前面几个创建型模式逐步讲为什么使用,而是关注怎么实现单例 的目的。

单例模式的实现

单例模式有两种实现方式:

  • Module-level Singleton
  • Classic Singleton

Module-level Singleton

通过模块文件的方式实现,一个模块本身就是一个单例。

Modules are “singletons” in Python because import only creates a single copy of each module; subsequent imports of the same name keep returning the same module object.

首先我们的文件结构如下:

.
├── test0.py
├── test2.py
└── utils.py

utils.py 中的代码:

print("import utils:")
class Man:
    common = {}
    def __init__(self):
        self.shared = {}
    
man = Man()
shared_value = "hello"

test0.py 中的代码:

import utils

print(utils.shared_value)
utils.shared_value += " add by test0 module"
print(id(utils))
print(id(utils.man))

test2.py 中的代码:

import utils

print("*"*10)
print(utils.shared_value)
print(id(utils))
print(id(utils.man))

此时在终端中调试

>>> import test0
import utils:
hello
2264080776976
2264081025424

>>> import test2
**********
hello add by test0 module
2264080776976
2264081025424

可以看到在同一个进程中, test0test2 都在代码中执行过import utils,对比输出后可以得到下面的观察结果:

  1. 该模块只被执行一次(这可以从 import test0 的输出import utils获知)
  2. 两个代码中出现的utilsid 是一致的,这也意味着调用它们的属性也会是一致的,比如man 这个 Man 实例
  3. test0 中执行utils.shared_value修改影响到 test2 中的 utils.shared_value

而这就意味着我们用模块文件的形式实现了单例。

Classic Singleton

这是经典的单例实现方式,通过这种方式实现的类只会在没有其他实例存在时,才会创建一个实例,否则它只会返回已经创建的类实例。
相信大家都对 __init__ 很熟悉,但很少接触到 __new__, 而这种单例的实现就是基于这个对这个类型函数的修改。

首先要明白一个类进行实例化时会经过下面的调用流程:

调用 __new__ 创建实例
调用 __init__ 对实例进行初始化

而对 __new__ 的实现中主要两种形式:

  1. 定义一个类级别的字典变量,每次生成实例前判断字典中是否有值
  2. 定义一个类级别的属性,每次生成实例前判断这个属性是否为空或者是否存在

至于再将这种形式实现成装饰器还是单例类 也不过是额外发挥罢了。

# 利用 object instance
class SingletonClass(object):
    def __new__(cls):
        if not hasattr(cls, "instance"):
            cls.instance = super(SingletonClass, cls).__new__(cls)
        return cls.instance

# 利用字典变量 instance
class SingletonClassVersion2(object):
    instance = {}
    def __new__(cls):
        if "singleton" not in cls.instance:
            cls.instance["singleton"] = super(SingletonClass, cls).__new__(cls)
        return cls.instance["singleton"]

singleton = SingletonClass()
new_singleton = SingletonClass()

print(singleton is new_singleton)
print(id(singleton))
print(id(new_singleton))

但这种实现方式有一个问题:即它虽然阻止了类每一次调用__new__ 新建一个实例,但是它每次都会触发__init__ ,这意味着定义在__init__ 函数中的初始化操作会被重复执行,覆盖掉实例变量之前对实例变量的操作,因此共享数据需要保存在类变量中。

解决这个问题的思路就是修改__init__ 方法:

class SingletonClass(object):
    instance = None
    initialized = False
    
    def __new__(cls):
        if not cls.instance:
            cls.instance = super(SingletonClass, cls).__new__(cls)
        return cls.instance
    
    def __init__(self):
        if not self.initialized:
            # 这里放置初始化逻辑
            self.initialized = True
            print("Initializing SingletonClass instance")

单例模式实现的优化

线程安全问题

上面的 classic singleton 实现方式在并发程序中可能会出现多个线程同时访问类进行实例化时创建两个实例。为了确保同一时刻只有一个线程可以调用 __new__ ,需要增加锁机制和重复检查。

from threading import Lock

class SingletonClass(object):
	_lock = Lock()
    def __new__(cls):
	    if not hasattr(cls, "instance"):
            with cls._lock:
                if not hasattr(cls, "instance"):
    	            cls.instance = super(SingletonClass, cls).__new__(cls)
	    return cls.instance

可以看到程序中不但增加锁,还进行 double-check,这样做的原因可以看下面这张流程图,可以看到在 thread2 试图创建实例时,如果不再次进行实例检查(此时thread1 已经创建实例)就会创建又一个实例。

No
thread1释放锁
thread1
thread2
instance exists?
争夺锁资源
thread1 抢到
thread2 阻塞
创建实例
thread2 获取到锁资源
试图创建实例

违反单一职责原则

classic singleton的实现方式将维护一个实例的职责和类本身的功能职责组合在一起,这不利于维护。优化这个问题的两种思路:

  1. 作为类装饰器实现
  2. 作为 metaclass 使用

类装饰器的实现

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 Wizard:
    def __init__(self, name):
        self.name = name

这种方式的好处在于使用灵活并且清晰(只要使用该装饰器就知道这个类是单例)

metaclass方式实现

class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Wizard(metaclass=SingletonMeta):
    def __init__(self, name):
        self.name = name

单例模式的应用

  • 数据库连接的 connection 管理, 一般会使用一个连接池实例(单例模式),降低每次都要新建连接的开销
  • 常见的Logger 实例,项目中的代码使用一个共同的日志实例
  • 管理配置的实例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值