什么是 单例模式?
顾名思义 单例模式 便是指 一个类在一个运行的程序中只能有一个实例;
代码中的例子(何时该使用此模式):
- 程序中 对 某个固定文件的多次读取,可以使用单例模式,这样减少程序IO时间 和 生成新对象的 内存资源占用, 相当于 缓存功能;
单例模式 关键的角色:
- 单例 角色: 能创建全局唯一一个实例的类
该模式的主要优缺点如下:
示例代码部分(多个实现方式)
1.通过 metaclass方式实现
# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren
All rights reserved
create time '2020/11/1 14:45'
Usage:
此程序 生成类 和对象的顺序 或 关系为:
在程序 运行 到 class Foo(metaclass=SingletonType) 时 先调用 SingletonType的__new__,__init__ 方法 生成 Foo类
然后在 运行 到 obj = Foo('xx') 时 调用 SingletonType的__new__方法,其内部调用 Foo类的__new__,__init__方法 生成具体的 实例
总结: 先通过 type 生成 用户 类,然后 用户类才能具体实例化,而 生成用户类的过程 对应 type来说 就是对 type进行实例化
"""
class SingletonType(type):
"""
type 类 作用是 创建类的 一种类,在创建用户定义的类时,会先执行 type类的 __new__,__init__方法(相当于对 type类进行实例化,只是实例化的结果 为用户类) 生成对应用户类
在 用户类实例化时,会调用 type类的 __call__方法,__call__方法内部 会调用 对应用户类的 __new__,__init__ 方法 完成 创建实例对象和初始化功能
此 SingletonType 类 继承自 type 类,对其 __call__ 方法进行了修改,使 用户定义的 Foo类 改成了单例模式
用户定义的Foo类通过 metaclass实现 父类SingletonType 对子类Foo 的控制,而 一般继承关系中,父类是无法控制子类的
"""
_instance = None
def __init__(self, *args, **kwargs):
"""
对实例化后的用户类(实例化的结果 就是 用户类) 初始化时用到
:param args:
:param kwargs:
"""
super(SingletonType, self).__init__(*args, **kwargs)
def __new__(cls, *args, **kwargs):
"""
实例化用户类(实例化的结果 就是 用户类)
:param args:
:param kwargs:
"""
return type.__new__(cls, *args, **kwargs)
def __call__(cls, *args, **kwargs): # 这里的cls,即Foo类
"""
在 用户类 实例化为 具体对象时调用
:param args:
:param kwargs:
:return:
"""
# 判断 实例是否已经存在
if not cls._instance:
obj = cls.__new__(cls, *args, **kwargs)
cls.__init__(obj, *args, **kwargs) # Foo.__init__(obj)
cls._instance = obj
else:
obj = cls._instance
return obj
class Foo(
metaclass=SingletonType): # 指定创建Foo的type为SingletonType,通过 debug模式 运行到此时 会 通过调用 SingletonType类 的 __new__,__init__方法 创建 Foo的类,所以 用户定义的python类 都是 type这个类的实例
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
return object.__new__(cls)
def __str__(self):
return self.name
obj = Foo('刚开始初始化的对象')
obj1 = Foo('后续生成的对象')
print(id(obj), obj)
print(id(obj1), obj)
2.使用 __new__方法实现
# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren
All rights reserved
create time '2020/10/29 19:52'
Usage:
使用 __new__ 方法实现
"""
class Foo:
_instance = None
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
"""
实例化 时 通过 cls._instance 判断是否之前实例化过
:param cls: 类
:param args:
:param kwargs:
"""
if not cls._instance:
# 通过object实例化对象
obj = object.__new__(cls)
cls._instance = obj
else:
obj = cls._instance
return obj
foo1 = Foo('foo1')
foo2 = Foo('foo2')
print(id(foo1))
print(id(foo2))
3.通过 装饰器 实现
# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren
All rights reserved
create time '2020/10/29 19:52'
Usage:
使用 装饰器 方法实现 单例模式
"""
def singleton(cls):
"""
单例模式 装饰器
:param cls:
:return:
"""
def _singleton(*args, **kwargs):
"""
通过 cls._instance 判断是否之前实例化过
:param args:
:param kwargs:
:return:
"""
if not cls._instance:
obj = cls(*args, **kwargs)
cls._instance = obj
else:
obj = cls._instance
return obj
return _singleton
@singleton
class Foo:
_instance = None
def __init__(self, name):
self.name = name
foo1 = Foo('foo1')
foo2 = Foo('foo2')
print(id(foo1))
print(id(foo2))
4.通过 模块 实现(此方法 在多线程 也是安全的)
# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren
All rights reserved
create time '2020/10/29 19:52'
Usage:
使用 模块 方法实现 单例模式
模块 在第一次导入时 生成 .pyc文件,第二次导入时 直接加载 .pyc文件,不会再执行 模块内的代码
"""
class Foo:
_instance = None
def __init__(self):
print('初始化对象')
def read_file(self):
"""
:return:
"""
print('此处执行 读取文件等操作,此模块 实现单例的 类')
foo = Foo()
调用时
# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren
All rights reserved
create time '2020/11/1 18:14'
Usage:
"""
from design_pattern.model.singleton_model import foo
def ge_foo():
print('第一次调用 foo')
foo.read_file()
if __name__ == '__main__':
ge_foo()
5.使用 类 + 类方法 实现 (不推荐使用,因为需要 调用类方法,多此一举)
# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren
All rights reserved
create time '2020/11/1 14:45'
Usage:
使用 类 实现 单例模式
"""
class Foo:
_instance = None
def __init__(self, name):
"""
:param name:
"""
self.name = name
@classmethod
def instance(cls, name):
"""
创建对象
:param args:
:param kwargs:
:return:
"""
if not cls._instance:
print('新建实例')
cls._instance = Foo(name)
return cls._instance
def __str__(self):
"""
:return:
"""
return self.name
obj = Foo.instance('第一次实例化')
print(id(obj), obj)
obj1 = Foo.instance('第二次')
print(id(obj1), obj1)
线程安全问题
在 如上所有实例代码中 均 是 在 单线程中运行的示例,在多线程中, 如果出现耗时IO操作等 可能 会导致 单例模式 失效(一个类多个实例),此时 需要添加 线程锁 来保证唯一性(但是 损失了 速度)
如下 代码 简要 表示 多线程中 的 单例模式 实现方式
# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren
All rights reserved
create time '2020/10/29 19:52'
Usage:
使用 装饰器 方法实现 多线程下 安全的单例模式
"""
import threading
import time
def singleton(cls):
"""
单例模式 装饰器
:param cls:
:return:
"""
# 线程锁
_instance_lock = threading.Lock()
def _singleton(*args, **kwargs):
"""
通过 cls._instance 判断是否之前实例化过
:param args:
:param kwargs:
:return:
"""
# 2次检查 实例是否存在
if not cls._instance:
with _instance_lock:
if not cls._instance:
obj = cls(*args, **kwargs)
cls._instance = obj
return obj
obj = cls._instance
return obj
return _singleton
@singleton
class Foo:
_instance = None
def __init__(self, name):
"""
:param name:
"""
# 表示 耗时操作
time.sleep(1)
self.name = name
def task(arg):
obj = Foo(arg)
print(obj.name, id(obj))
for i in range(10):
t = threading.Thread(target=task, args=[i, ])
t.start()
拓展部分:
根据 单例模式的特点,一个 类只能有一个示例,后续的创建 都是调用的同一个内存地址的 对象,相当于 是 直接 读取的缓存数据,那么 便 可以据此 对 单例模式 进行 简单修改 为 不同参数 存不同缓存 对象,相同参数 则 直接返回对应 对象的 缓存功能(类似redis缓存);
# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren <rgc@bvrft.com>
All rights reserved
create time '2020/10/29 19:52'
Usage:
使用 装饰器 方法实现 单例模式 的拓展功能 -- 参数校验 级别 的 缓存
"""
import random
import time
def singleton_cache(cls):
"""
单例模式 根据 被装饰 类 初始化 的 参数 进行判断,如果参数已存在,则直接返回对应示例,否则 新建实例,并存入 缓存dict 则装饰器
:param cls:
:return:
"""
def _singleton(*args, **kwargs):
"""
通过 cls._cache 判断是否之前实例化过
:param args:
:param kwargs:
:return:
"""
key = str(*args)
# 通过 key 判断 之前是否生成过 对应示例
if key not in cls._cache:
obj = cls(*args, **kwargs)
# 存入缓存
cls._cache[key] = obj
else:
# 取缓存
obj = cls._cache[key]
return obj
return _singleton
@singleton_cache
class Foo:
_cache = {}
def __init__(self, name):
"""
:param name:
"""
# 表示 耗时操作
self.name = name
self.val = self._action()
def _action(self):
"""
表示 耗时操作
:return:
"""
time.sleep(1)
return random.randint(1, 100)
def __str__(self):
return f'取出缓存数据,key:{str(self.name)},value:{str(self.val)}'
if __name__ == '__main__':
start_time = time.time()
# 新建,存入缓存
foo1 = Foo('foo1')
# 新建,存入缓存
foo2 = Foo('foo2')
# 读取缓存
foo3 = Foo('foo1')
print(id(foo1), foo1)
print(id(foo2), foo2)
print(id(foo3), foo3)
print(f'耗时:{time.time() - start_time}') # 耗时2s,因为 foo3 是读取的缓存数据
总结:
单例模式非常容易理解,比其他几个创建型 模式 要简单很多, 但是其实现方式 却有很多种,而且 涉及到python高级语法及 多线程安全问题,所以 重点应该放在 实现方式上;
可以根据 单例模式的 特点 进行 灵活运用,从而 实现 参数不同,获取 对象 不同 的 缓存功能;
可以 根据 单例模式的特点 限制 生成 实例的 数量;
相关链接:
https://zhuanlan.zhihu.com/p/98440398
https://www.cnblogs.com/huchong/p/8244279.html