一些Pythonic技巧实现的单例模式

  • 目的
    单例模式可以保证一个类只有一个实例,并提供一个访问它的全局访问点。什么情况下需要一个类只能创建一个实例这种情况呢?举一些例子,操作系统只能有一个文件系统,一个窗口管理器。

  • 方法
    为了实现单列模式,我们可以让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建(通过截取创建新对象的请求),并且它可以提供一个访问该实例的方法。

  • 使用
    当类只能有一个实例而且调用端可以从一个公共入口访问。
    作为父类,用来扩展其他只能唯一实例化的类

  • 注意
    注意创建过程中的线程安全性。由于该模式需要一个静态变量来保存唯一的实例,为了线程安全需要改静态变量的线程安全。下面的实现保证线程安全采用加锁的方法。

  • 单例模式中的主动实例化和被动实例化
    主动实例化指在加载主类的时候就实例化单例类,并保存到类的静态变量中,它是线程安全的。被动实例化指在首次调用单例类静态方法或构造器时创建。问题:Python可以实现主动实例化吗?

实现

下面采用两种语言实现,同时考虑线程不安全的情况。

采用类变量是保存唯一实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Singleton:
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance

class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance

class Demo(Singleton):
spam = 1

考虑线程安全的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import threading

class Singleton:
def __new__(cls, *args, **kwargs):
with threading.Lock():
if not hasattr(cls, '_instance'):
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance

# 这种办法线程安全,但效率不高,没有必要一开始就加锁。可以先检查是否有实例化,
# 然后再加锁检查时候有实例化。第一次检查是为了是否需要进入加锁创建状态,第二
# 次检查是确认加锁前是否已经有现车实例化该类。

class Singleton:
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
with threading.Lock():
if not hasattr(cls, '_instance'):
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance

class Demo(Singleton):
spam = 1

使用元类方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Singleton(type):
def __init__(self, *args, **kwargs):
self.__instance = None
super().__init__(*args, **kwargs)

def __call__(self, *args, **kwargs):
if self.__instance is None:
super().__call__(*args, **kwargs)
return self._instance
return self.__instance

class Demo(metaclass=Singleton):
spam = 1

# 这种方法线程不安全,但更高级。其本质上和第一中的实现方式是一致的,
# 都采用静态变量来保存唯一实例。但采用元类的方式更使元类在创建类的
# 时候动态创建这个静态变量。如果有不同的类同时需要实现单列模式,但
# 它们又要求不能有同一父类,这种情况可以使用元类的实现。下面是线程
# 安全的方法,为了考虑性能,这一依旧采用先检测再加锁的方法。

class Singleton(type):
def __init__(self, *args, **kwargs):
self.__instance = None
super().__init__(*args, **kwargs)

def __call__(self, *args, **kwargs):
if self.__instance is None:
with threading.Lock():
if self.__instance is None:
super().__call__(*args, **kwargs)
return self.__instance

转载请包括本文地址:https://allenwind.github.io/blog/2455
更多文章请参考:https://allenwind.github.io/blog/archives/