设计模式之单例模式
【一】什么是单例模式
- 单例设计模式(Singleton Design Pattern): 一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
- 当一个类的功能比较单一,只需要一个实例对象就可以完成需求时,就可以使用单例模式来节省内存资源。
【通常】单例模式创建的对象是进程唯一的, 单例类中对象的唯一性的作用范围是进程内的,在进程间是不唯一的。
【二】如何实现一个单例
-
要实现一个单例,我们需要知道要重点关注的点是哪些?
-
- 考虑对象创建时的线程安全问题
- 考虑是否支持延迟加载
- 考虑获取实例的性能是否高(是否加锁)
-
在python中,我们可以使用多种方法来实现单例模式
-
- 使用模块
- 使用装饰器
- 使用类(方法)
- 基于__new__方法实现
- 基于元类metaclass实现
【三】不支持延迟加载
在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。不过,这样的实现方式不支持延迟加载。
- 一种常见的方式是利用python模块化实现单例。
# test.py
import threading
class IdGenerator:
def __init__(self):
self._lock = threading.Lock()
self.id = 0
def get_id(self):
with self._lock:
self.id += 1
return self.id
# 创建单例实例
id_generator_instance = IdGenerator()
- 在另一个文件(例如 test_user.py)中导入模块并使用单例模式
# test_user.py
import test
if __name__ == '__main__':
singleton1 = test.id_generator_instance
singleton2 = test.id_generator_instance
print(singleton1 is singleton2) # 输出 True,表示是同一个实例
- Python中可以使用模块的方式非常简单地实现单例模式,因为Python模块在程序中只会被导入一次,所以它天生就是单例的。
【四】支持延迟加载
-
- 使用装饰器
- 使用类(方法)
- 基于__new__方法实现
- 基于元类metaclass实现
【五】类属性
【1】类产生对象
- 当我们有一个类,类里面有很多方法,我们想使用这些方法的时候,我们的第一想法是实例化得到一个对象,再用对象去调用相应的方法
- 但是不同的人,都想用这一个方法的时候,就需要每一个人都去实例化对象,用对象调用方法,但是我们不去使用对象中的其他属性,只用到其中一种属性,于是这对于系统来说是一种开销
# 创建一个普通的类
class MysqlControl(object):
pass
# 实例化类得到对象 --- 类只要加 () 实例化就会产生一个全新的对象
obj_one = MysqlControl()
obj_two = MysqlControl()
# 查看对象 --- 发现虽然是同一个类实例化得到的对象,但是对象的地址不一样
print(obj_one)
# <__main__.MysqlControl object at 0x0000023E6330C880>
print(obj_two)
# <__main__.MysqlControl object at 0x0000023E6330CBE0>
print(obj_one is obj_two)
# False
【2】类属性包装成方法
- 使用类属性保存实例,通过类方法获取实例。
- 在第一次调用get_instance方法时创建实例,并在后续调用中直接返回该实例。
class Quantong(object):
_instance = None
def __init__(self, ip, port):
self.ip = ip
self.port = port
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = Quantong('741.0.0.1', 9527)
return cls._instance
obj_one = Quantong.get_instance()
obj_two = Quantong.get_instance()
print(obj_one)
# <__main__.Quantong object at 0x000001D4DCFC9D80>
print(obj_two)
# <__main__.Quantong object at 0x000001D4DCFC9D80>
print(obj_one is obj_two)
# True
# 输出 True,表示是同一个实例
【六】装饰器
- 使用装饰器将原来的类包装成一个新的类,通过闭包和字典保存实例。
- 在每次实例化时,先检查字典中是否已经存在该类的实例,如果不存在才创建实例并返回。
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class Singleton:
pass
singleton_one = Singleton()
singleton_two = Singleton()
print(singleton_one)
# <__main__.Singleton object at 0x00000213FD69A800>
print(singleton_two)
# <__main__.Singleton object at 0x00000213FD69A800>
print(singleton_one is singleton_two)
# True
# 输出 True,表示是同一个实例
# # 使用 __call__ 方法获取单例
singleton_three = Singleton()
print(singleton_one is singleton_three)
# True
【七】元类(metaclass)
- 定义一个元类,在元类的__call__方法中判断实例是否已存在,如果不存在则调用父类的__call__方法来创建并返回实例。
class SingletonType(type):
def __init__(cls, name, bases, attrs):
super(SingletonType, cls).__init__(name, bases, attrs)
cls.instance = None
def __call__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super(SingletonType, cls).__call__(*args, **kwargs)
return cls.instance
class Singleton(metaclass=SingletonType):
pass
singleton_one = Singleton()
singleton_two = Singleton()
print(singleton_one)
# <__main__.Singleton object at 0x0000018BA9217E80>
print(singleton_two)
# <__main__.Singleton object at 0x0000018BA9217E80>
print(singleton_one is singleton_two)
# True
# 输出 True,表示是同一个实例
# # 使用 __call__ 方法获取单例
singleton_three = Singleton()
print(singleton_one is singleton_three)
# True
【八】基于__new__方法
【1】推理思路
-
在Python中,对象的实例化过程通常遵循以下步骤:
-
- 首先,执行类的__new__方法,如果未定义此方法,将默认调用父类的__new__方法来创建一个实例化对象。
- 接着,再执行__init__方法来对这个新创建的对象进行初始化。
-
我们可以充分利用这个实例化过程来实现单例模式。
-
- 具体做法是在类的__new__方法中判断是否已经存在实例,如果存在,则直接返回现有的实例,否则创建一个新的实例。
- 这样就能够确保只有一个实例存在,从而实现了单例模式的效果。
【2】实现
- 重写__new__方法,在实例化对象时判断类中是否已有实例,如果没有则调用父类的__new__方法来创建并返回。
优势:
- 这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。
class Singleton(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super().__new__(cls)
return cls._instance
singleton_one = Singleton()
singleton_two = Singleton()
print(singleton_one)
# <__main__.Singleton object at 0x00000209D2D3CBE0>
print(singleton_two)
# <__main__.Singleton object at 0x00000209D2D3CBE0>
print(singleton_one is singleton_two)
# True
# 输出 True,表示是同一个实例
# # 使用 __call__ 方法获取单例
singleton_three = Singleton()
print(singleton_one is singleton_three)
# True
【九】基于模块
- 将实例化操作放在模块级别,通过导入该模块来获取实例。
- 由于Python模块在运行时只会被导入一次,因此保证了实例的单一性。
# singleton.py
class Singleton:
pass
singleton_instance = Singleton()
【十】pickle模块介绍
【1】什么是pickle模块
- pickle 模块是 Python 内置的一个序列化和反序列化的模块,它可以将 Python 对象转换为字节流,也可以将字节流转换回 Python 对象。
- 这些操作通常被称为序列化和反序列化。
【2】序列化和反序列化
(1)序列化(dumps)
- 序列化是指将 Python 对象转换为可以存储在文件、数据库或者网络传输的数据格式的过程。
- 序列化后得到的结果通常是字节流形式的数据,这种数据可以方便地进行存储和传输。
import pickle
dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
str_dic = pickle.dumps(dic)
print(str_dic)
# b'\x80\x04\x95#\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x02k1\x94\x8c\x02v1\x94\x8c\x02k2\x94\x8c\x02v2\x94\x8c\x02k3\x94\x8c\x02v3\x94u.'
print(type(str_dic))
# <class 'bytes'>
(2)反序列化(loads)
- 反序列化则是指将字节流形式的数据恢复为原来的 Python 对象的过程。
- 在反序列化过程中,Python 解释器会按照一定的规则解析字节流,并根据字节流的内容创建出与之对应的 Python 对象。
import pickle
dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
str_dic = pickle.dumps(dic)
print(str_dic)
# b'\x80\x04\x95#\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x02k1\x94\x8c\x02v1\x94\x8c\x02k2\x94\x8c\x02v2\x94\x8c\x02k3\x94\x8c\x02v3\x94u.'
print(type(str_dic))
# <class 'bytes'>
dic2 = pickle.loads(str_dic)
print(dic2)
# {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
print(type(dic2))
# <class 'dict'>
【3】常用的方法
(1)Python对象和pickle对象互转
- pickle 模块提供了一系列的方法和类来实现序列化和反序列化操作。
- 其中最常用的方法是 pickle.dumps() 和 pickle.loads()。
- pickle.dumps() 方法接受一个 Python 对象作为参数,返回一个字节流,表示该对象的序列化结果。
- pickle.loads() 方法接受一个字节流作为参数,返回一个 Python 对象,表示该字节流的反序列化结果。
import pickle
# 【一】将Python中的字典类型序列化成pickle的字节流
# 【1】字典类型
user_dict = pickle.dumps({'name': 'dream'})
print(user_dict, type(user_dict))
# b'\x80\x04\x95\x13\x00\x00\x00\x00\x00\x00\x00}\x94\x8c\x04name\x94\x8c\x05dream\x94s.' <class 'bytes'>
# 【2】字符串类型
user_str = pickle.dumps('hello world')
print(user_str, type(user_str))
# b'\x80\x04\x95\x0f\x00\x00\x00\x00\x00\x00\x00\x8c\x0bhello world\x94.' <class 'bytes'>
# 【3】整数类型
user_int = pickle.dumps(123)
print(user_int, type(user_int))
# b'\x80\x04K{.' <class 'bytes'>
# 【4】浮点数类型
user_float = pickle.dumps(3.14)
print(user_float, type(user_float))
# b'\x80\x04\x95\n\x00\x00\x00\x00\x00\x00\x00G@\t\x1e\xb8Q\xeb\x85\x1f.' <class 'bytes'>
# 【5】布尔类型
user_bool = pickle.dumps(True)
print(user_bool, type(user_bool))
# b'\x80\x04\x88.' <class 'bytes'>
# 【6】元组类型
user_tuple = pickle.dumps((1, 2, 3))
print(user_tuple, type(user_tuple))
# b'\x80\x04\x95\t\x00\x00\x00\x00\x00\x00\x00K\x01K\x02K\x03\x87\x94.' <class 'bytes'>
# 【7】列表类型
user_list = pickle.dumps([1, 2, 3])
print(user_list, type(user_list))
# b'\x80\x04\x95\x0b\x00\x00\x00\x00\x00\x00\x00]\x94(K\x01K\x02K\x03e.' <class 'bytes'>
# 【8】集合类型
user_set = pickle.dumps({1, 2, 3})
print(user_set, type(user_set))
# b'\x80\x04\x95\x0b\x00\x00\x00\x00\x00\x00\x00\x8f\x94(K\x01K\x02K\x03\x90.' <class 'bytes'>
(2)Python对象和pickle对象读写
import pickle
# 【二】数据的读写
def save_data(path, mode='wb', data=None):
with open(path, mode) as fp:
pickle.dump(data, fp)
print(f'当前已保存!保存路径为:{path}!')
def load_data(path, mode='rb'):
with open(path, mode) as fp:
data = pickle.load(fp)
return data
# 【1】序列化写入 Python独有的类
class Student(object):
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
student = Student('张三', 18, '高三')
# 将实例化好的对象写入到指定文件
# save_data(path='Student', data=student)
# 将保存好的数据读出来,可以直接使用
student_data = load_data(path='Student')
print(student_data) # <__main__.Student object at 0x109277eb0>
print(student_data.name) # 张三
print(student_data.age) # 18
【4】pickle的弊端
- 需要注意的是,pickle 模块并不安全,因为它可以序列化任何 Python 对象,包括内置类型、自定义类的对象等。
- 这使得 pickle 模块有可能成为攻击者攻击的目标。
- 例如,攻击者可以通过构造恶意的 pickle 数据包,将其发送给受害者,导致受害者执行任意代码。
- 因此,如果可能的话,应该尽量避免使用 pickle 模块,或者至少要确保输入的数据来自可信的来源。