使用python实现单例模式的三种方式

2023-03-06 19:42:58 浏览数 (1)

# 0 . 前言

在整个进程中,有且只有一个对象存在,在任何地点使用都是同一个对象,可以解决多线程资源竞争问题,也常用于配置信息。

本文主要介绍使用python的三种实现单例模式的方式。

# 1. 在类中__new__方法中实现

在需要实现单例的 class 中添加__new__方法,在创建该 class 对象时会调用该方法,使用类变量 _instance 来保存当前对象,每次创建之前都会判断是否有该对象,没有则创建,有则直接返回。

代码语言:javascript复制
from typing import Any

class A:
    def __new__(cls, *args, **kwargs) -> Any:
        if not hasattr(cls, "_instance"):
            cls._instance = super().__new__(cls, *args, **kwargs)
    return cls._instance

我们创建两个 class A 对象,然后分别打印他们的内存 ID,会发现两者 ID 是一致的,也就是是同一个对象。

代码语言:javascript复制
a1 = A()  
a2 = A()  
print(id(a1))  
print(id(a2))

Output: 
2659742107728
2659742107728

# 2. 通过元类实现

上面的方式需要在每一个单例类中都要添加一个__new__方法,有大量的重复代码。接下来我们介绍通过元类来实现单例。

  • 第一版

首先创建 class Singleton 来继承 type,该类为我们自定义的元类。然后创建我们需要单例的 class A 和 B,它们都需要通过 metaclass=Singleton 来选择 Singleton 作为它们的元类。

在元类中,创建 __call__ 方法,该方法会在 class A 和 B 创建对象时调用,在该方法中会调用 __new____init__ 方法,创建完对象后,再将该对象放在类变量 _instance 中,和 1. 在__new__中实现单例 的方法一样。

代码语言:javascript复制
from typing import Any

class Singleton(type):
    def __call__(self, *args: Any, **kwds: Any) -> Any:
        if not hasattr(self, "_instance"):
            self._instance = super(Singleton, self).__call__(*args, **kwds)

        return self._instance

class A(metaclass=Singleton):
    pass

class B(metaclass=Singleton):
    pass

我们再通过测试,可以看到 class A 和 B 都实现了单例。

代码语言:javascript复制
a1 = A()
a2 = A()
print(id(a1))
print(id(a2))

b1 = B()
b2 = B()
print(id(b1))
print(id(b2))

Output:
>>> 2802632572784
>>> 2802632572784
>>> 2802632572400
>>> 2802632572400

但这个单例设计只适用于单线程,在多线程中,如果两个线程都停留 hasattr 下面,可能还是会创建两个对象,达不到单例的效果。

  • 第二版

通过加锁解决上面并发的问题。

代码语言:javascript复制
import threading

class Singleton(type):
    lock = threading.Lock()
    
    def __call__(self, *args: Any, **kwds: Any) -> Any:
        with Singleton.lock:
            if not hasattr(self, "_instance"):
                self._instance = super(Singleton, self).__call__(*args, **kwds)

        return self._instance

但是其实只有第一次创建对象时,需要通过锁同步获取单例对象,在已有对象时,不需要再用锁了,在这种情况下,每次获取对象都经过锁,会影响性能。

  • 最终版

所以再加上一重判断,减少每次锁判断带来的性能消耗。

代码语言:javascript复制
import threading
class Singleton(type):
    lock = threading.Lock()
    
    def __call__(self, *args: Any, **kwds: Any) -> Any:
        if not hasattr(self, "_instance"):
            with Singleton.lock:
                if not hasattr(self, "_instance"):
                    self._instance = super(Singleton, self).__call__(*args, **kwds)

        return self._instance

# 3. 通过装饰器实现单例

该方法是通过实现一个装饰器,在需要实现类上添加该装饰器即可完成,使用简单。

通过将所有的单例对象保存在装饰器的 _instance 字典中,以类为 key,对象为 value 进行存储。

代码语言:javascript复制
def Singleton(cls):
    _instance = {}
    def _singleton(*args, **kargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kargs)
        return _instance[cls]
    return _singleton
@Singleton
class A:
    pass
  
@Singleton
class B:
    pass

0 人点赞