Python-单例模式

本文最后更新于:2020年11月6日 下午

单例模式

单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在
当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场

在那些需要“创建连接”的内容中使用能节约很多资源

Python实现方式

模块方式

其实,Python 的模块就是天然的单例模式
因为模块在第一次导入时,会生成.pyc文件,当第二次导入时,就会直接加载.pyc文件,而不会再次执行模块代码
因此,只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了

例:

single.py
1
2
3
4
class Singleton(object):
def foo(self):
pass
single = Singleton()

直接在其他文件中导入此文件中的对象single,这个对象即是单例模式的对象

a.py
1
2
3
from demo.my_singleton import single

single.foo()

装饰器方式

函数装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def singleton(cls):
_instance = {}

def inner():
if cls not in _instance:
_instance[cls] = cls()
return _instance[cls]
return inner

@singleton
class Cls(object):
def __init__(self):
pass

cls1, cls2 = Cls(), Cls()
print(id(cls1) == id(cls2))
  • id函数可以查看对象在内存中的位置
  • 使用这个方式创建对象时,会访问singleton函数
    实际上,由于装饰器的应用,Cls返回的结果也变成了函数

函数会查看想要创建实例的类是否在字典中已经有记录,要是有记录就直接返回记录中的对象

类装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Singleton(object):
_INSTANCE = {}
def __init__(self, cls):
self.cls = cls
def __call__(self, *args, **kwargs):
instance = self._INSTANCE.get(self.cls, None)
if not instance:
instance = self.cls(*args, **kwargs)
self._INSTANCE[self.cls] = instance
return instance
def __getattr__(self, key):
return getattr(self.cls, key, None)
@Singleton
class my_cls(object):
pass

cls1, cls2 = Cls2(), Cls2()
print(id(cls1) == id(cls2))

虽然和函数装饰器看上去并不一样,但实际上原理是一样的

类方式

使用属性(不要在多线程环境使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import time
class Singleton(object):
__instance = None

def __init__(self, x):
self.x = x
print(x)
time.sleep(1) # 加入干扰元素,造成多线程出现问题

@classmethod
def my_singleton(cls, *args, **kwargs):
with cls.__instance_lock: # 加锁
if not cls.__instance:
cls.__instance = cls(*args, **kwargs)
return cls.__instance

这种方式如果不加锁,在多线程环境使用的时候会出现失效的情况

过程很容易想象,假设现在有A,B两个线程

  1. A if not cls.__instance:判断为真
  2. B if not cls.__instance:判断为真
  3. B cls.__instance = cls(*args, **kwargs)初始化了一遍实例
  4. A cls.__instance = cls(*args, **kwargs)初始化了两遍实例

这种方式仅在单线程情况下推荐使用
多线程模式中不推荐使用,加锁会明显的降低性能

使用__new__

当我们实例化一个对象时,是先执行了类的__new__方法实例化对象;然后再执行类的__init__方法,对这个对象进行初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import threading


class Singleton(object):
_instance_lock = threading.Lock()

def __init__(self, x):
self.x = x
import time
time.sleep(1) # 加入干扰元素,造成多线程出现问题

def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
with cls._instance_lock: # 加锁
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance

元类方式

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton(type):
def __init__(cls, name, bases, dic):
super(Singleton, cls).__init__(name, bases, dic)
cls._instance = None
def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
# cls._instance = cls(*args, **kwargs) # Error! Lead to call this function recursively
return cls._instance

class my_cls(object):
__metaclass__ = Singleton

这个例子中我们使用元类Singleton替代默认使用type方式创建类my_cls
可以将类my_cls看做是元类Singleton的一个对象,当我们使用my_cls(...)的方式创建类my_cls的对象时,实际上是在调用元类Singleton的对象my_cls

对象可以以函数的方式被调用,那么要求类中定义__call__函数。不过此处被调用的是类,因此我们在元类中定义函数__call__来控制类my_cls对象创建的唯一性

这种方式的弊端之一就是类唯一的对象被存放在类的一个静态数据成员中,外部可以通过class_name._instance的方式修改甚至删除这个实例(该例中my_cls._instance = None完全合法)