你好,我是kelly,今天分享:Python单例模式,这是常见的设计模式之一。
什么是单例模式?
在Python代码运行过程中,一个类只允许创建一个实例,即内存中只存在一个类实例。
单例模式的常见使用场景:
-
数据库连接:程序只有一个数据库连接对象
-
配置信息:程序只有一个全局配置对象
-
日志记录器:程序只有一个日志记录器对象
Python单例的实现方法大体有下面的5种:
-
使用模块
-
使用装饰器
-
使用类
-
基于__new__方法
-
使用元类
平时用的最多还是第1和第4,其中第1种使用最方便,笔者也最常用,使用时不会出错。这里只介绍第1和第4种。
一、使用模块
前提知识:在Python中,模块(.py文件)天然是单例的,模块只会被加载一次。
新建design_singleton.py文件,存放单例的定义
class ToolUtil(object):
def __init__(self, name):
self.name = name
def do_cupboard(self):
print("{} 能打开柜子".format(self.name))
def do_screw(self):
print("{} 能拧螺丝".format(self.name))
tool_util = ToolUtil(name="工具集")
新建main.py文件,使用已定义的单例
from design_singleton import tool_util
tool1 = tool_util
tool2 = tool_util
tool3 = tool_util
print(id(tool1))
print(id(tool2))
print(id(tool3))
运行结果:
1486273127904
1486273127904
1486273127904
不同tool(1、2、3)变量的内存地址相同,说明只存在一个类实例。
二、使用__new__方法
思想:在创建类实例时,判断是否已存在类实例,若存在则直接返回,否则创建新实例。
需要用到下面2个Python魔法方法:
-
__new__:用来创建类实例(分配类实例内存空间,并返回类实例的引用(内存地址))
-
__init__:用来对类实例进行初始化赋值
根据上述思想可以得到实现代码:
class ToolUtil(object):
_instance = None # 类属性,保存类实例
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
# 创建类对象时不需要args和kwargs参数
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
tool1 = ToolUtil("工具集1")
print(id(tool1), tool1.name)
tool2 = ToolUtil("工具集2")
print(id(tool2), tool2.name)
tool3 = ToolUtil("工具集3")
print(id(tool3), tool3.name)
运行结果:
3049865651104 工具集1
3049865651104 工具集2
3049865651104 工具集3
不同tool(1、2、3)变量的确对应同个内存地址,内存中确实只存在一个实例。
但是,上面的实现存在2个问题:
-
问题1:类实例会多次赋值,后一次赋值覆盖前一次赋值
-
问题2:线程安全
问题1:类实例会多次赋值
解决方法:在对类实例初始化赋值时,进行控制。
class ToolUtilV2(object):
_instance = None
def __init__(self, name):
# 对类实例,初始化赋值
if not hasattr(self, "name"):
self.name = name
def __new__(cls, *args, **kwargs):
# 创建类实例时不需要args和kwargs参数
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
tool1 = ToolUtilV2("工具集1")
print(id(tool1), tool1.name)
tool2 = ToolUtilV2("工具集2")
print(id(tool2), tool2.name)
tool3 = ToolUtilV2("工具集3")
print(id(tool3), tool3.name)
运行结果:
1737772278688 工具集1
1737772278688 工具集1
1737772278688 工具集1
类实例只会存在第一次赋值。
问题2:线程安全
解决方法:在多线程环境下存在线程安全问题,需要使用锁机制保证。
import threading
class ToolUtilV3(object):
_lock = threading.RLock() # 加锁,提供线程安全
_instance = None
def __init__(self, name):
if not hasattr(self, "name"):
self.name = name
def __new__(cls, *args, **kwargs):
# 创建类对象时不需要args和kwargs参数
if cls._instance is None:
with cls._lock:
cls._instance = super().__new__(cls)
return cls._instance
tool1 = ToolUtilV3("工具集1")
print(id(tool1), tool1.name)
tool2 = ToolUtilV3("工具集2")
print(id(tool2), tool2.name)
tool3 = ToolUtilV3("工具集3")
print(id(tool3), tool3.name)
运行结果:
1854786707712 工具集1
1854786707712 工具集1
1854786707712 工具集1
好了,今天的分享就到这里。
本文原始版本发表链接:
kelly会在公众号「kelly学技术」不定期更新文章,感兴趣的朋友可以关注一下,期待与您交流。
--over--