Python单例模式的设计与实现【完美版】
1. 按
众所周知,对象是解决继承的问题的,如果没有对象,那么将很难解决继承的问题。这点儿和人类的现实世界有相似之处,没有对象的话何谈子嗣的继承问题?
没有对象,将意味着很难实现继承时的多态、很难去塑造子类,没有子类又将意味着父类在设计时要具备所有的功能,这绝对是极不现实的。一种很现实的设计是:父类先解决一些问题,然后子类再解决一些问题,接着子类的子类再解决一些问题,这样子子孙孙无穷尽也,一定能够把所有问题都解决好的,这种设计模式也一定能够应对复杂多变的现实环境,因此对象的存在意义重大。
但对象是越多越好吗?答案绝对是否定的,很多情况下我们只需要一个对象就可以了,多余的对象会带来很多的不必要的开销和麻烦。这是因为每个对象都要占据一定的内存空间和CPU算力,大多数情况下我们只需要一个对象去执行任务,对于一颗CPU的核心而言,操作一个对象是最快的,为什么?这主要是由于线程的切换会造成不必要的路程开销。
设想一下,假如你有一个对象可以用来接吻,你的DNA序列约定你一生要完成4800次接吻才算完成了任务。这样在你临死的时候才会死而无憾,即程序完成所有的任务后正常退出,返回值为0,代表剩余待做的任务数为0。平均你和一个对象每天的最多能接吻48次,这样只和单个对象全力接吻的话100天就能完成任务。
但如果你有多个对象的话,你的平均作战能力并不会提升,这是因为每天接吻48次是你的处理极限,相反,你因为要和多个对象进行接吻,每次从一个对象移动到另一个对象那里会产生不必要的路程开销,会大大影响你的工作效率。
假设你有两个对象,对象A在河南,对象B在河北,你从河南到河北需要一天的时间,你每天工作完了之后会切换到另一个对象那里。假设先从与对象A接吻开始,工作一天共接吻48次,但第二天你需要移动到对象B那里,我们知道,移动的过程中是没有对象可以接吻的,只有到达目的地找到人之后才能进入工作状态。
你切换任务的过程将消耗一天的时间,这一天等于白白浪费了。因为你今天并没有执行有意义的接吻工作,虽然你也在马不停蹄地忙碌,但那用于移动距离、寻找目标的忙碌对完成接吻任务而言是不必要的,也是没有意义的。这样的话,你是一天干活,一天用于切换任务,即从当前对象跑到另一个对象那里,平均下来,两天只能完成48次的接吻任务,这样的话你需要花费200天才能完成DNA上面约定的接吻任务量,效率比着单对象模式大打折扣。
这里只考虑完工作一天之后才切换目标的情况,假如你没有太多耐心,打一枪立马就换地方,即完成一次接吻之后立即就跑到另一个对象那里。这样的话你将消耗4900天的时间才能完成任务,效率极低,需要13年多才能完成任务。
再假设你有多个对象,你的耐心很少,打一枪换十个地方,即完成一次接吻之后,立马跑到下一个对象那里,但发现这个对象没化妆、没打扮,很难看,然后再立马跑到下一个对象那里,平均见十个对象才碰到一个满意的,这样的话你将消耗48100天才能完成任务,效率更低了,需要131多年才能完成任务,怕是你这辈子不能死而无憾了,即返回值不能为0了。
在假设你根本没有耐心,打一枪换n个地方,即完成了一次接吻之后,立马跑到下一个对象那里,但发现这个对象没化妆、没打扮,很难看,然后再立马跑到下一个对象那里,接着就不断地在重复这种死循环的奔波状态,因为毫无耐心,眼光又很高、很挑剔,这辈子都在寻找合适的对象用于接吻,但一直都找不到。这种现象在计算机的状态切换中被称为死锁,死锁是存在的,死锁一旦出现,程序就死了,不会再执行任务了,陷入了一直切换状态的情形中。这样的话程序不论跑多久都不能完成任务,强制退出或者意外中断,返回值都不可能为0。
在计算机中,类的对象又称为类的实例,因此我们把一个类只生成一个对象的模式称为单例模式。
以下是常见的几种创建单例的模式。
说明:我写的懒汉式与饿汉式和别人的命名刚好是相反的,这个感觉每个人的理解不同,叫什么名字无所谓啦,只要能理解思想就行。
2. 本文地址
- 博客园:https://www.cnblogs.com/coco56/p/11253656.html
- 简书:https://www.jianshu.com/p/4c47f8e3809b
- CSDN:https://blog.csdn.net/COCO56/article/details/97409050
3. 通过继承单例父类来实现
实测发现通过继承的方法来实现Python程序的单例模式设计是最完美的,之前尝试过使用装饰器来实现单例模式,但具体实践的时候会出现诸多问题。
比如如果在装饰器中添加了getInstance方法和Instance属性,那主流的IDE或者编辑器将无法推导出来有这个方法或属性,尽管你的语法是正确的,但由于编辑器无法推导出来,你将在编写代码的时候无法使用tab键快速补全,很不方便。
自身是为了自身的存在而存在的,用自身的存在去完善和改良自身的存在无疑是最好的选择。
前面介绍过,对象的存在其实是为了更好地解决继承的问题的。既然是解决继承的问题,那么无疑最好的选择就是用继承本身去解决继承。
具体做法为:建一个单例父类,这个类只解决单例模式的问题,然后所有需要使用单例模式的类全部继承自这个单例父类。
相当于老祖先把单例模式的问题解决了,孩子们只需要继承老祖先的基因就可以了,这样,孩子们天生就是单例模式(因为老祖先除了解决单例问题,其他什么问题都不去做,相当于穷尽毕生的力量将单例问题研究地透透的,孩子们因为继承自老祖先,本身肯定已经保留了老祖先的优良基因)。
经实际测试,这种用继承本身去解决继承相关问题的方法是完美的。
示例代码:
# -*- coding : utf-8 -*-
import threading
_instances = {}#相当于民政局的登记簿,用于记录每个单例类的专属对象
_lock = threading.Lock()#使用线程锁以确保线程安全
class SingletonClass(object):
global _instances, _lock
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of', cls, *args, **kwargs)
if cls in _instances: return _instances[cls]
_lock.acquire()#上锁
_instances[cls] = cls._instances = super().__new__(cls)
_lock.release()#解锁
return _instances[cls]
@classmethod #用于删除对象,此方法仅在必要时使用,因为如果反复的析构和构造对象的话是极其浪费资源的。
def delInstance(cls, *args, **kwargs):
if cls in _instances: _instances[cls].__del__(*args, **kwargs); del _instances[cls]
@classmethod #用于获取对象
def getInstance(cls, *args, **kwargs):
if cls in _instances: return _instances[cls]
return cls(*args, **kwargs)
class CopyTree(SingletonClass):
def __init__(self, *args, **kwargs): print('__init__ of', self, *args, **kwargs)
def __del__(self, *args, **kwargs): print('__del__ of', self, *args, **kwargs)
class CopySubTree(CopyTree):
pass
class CopyBCSubTree(CopyTree):
pass
if __name__ == "__main__":
#调用构造函数和调用getInstance方法虽然都是获得的单例。但对于已存在的对象来说:调用构造函数,会重新调用__init__方法再初始化一遍儿此对象。
cls =CopyTree; print(cls); a = cls(); b = cls(); print('a=', a, 'b=', b)
c = cls.getInstance(); d = cls.getInstance(); print('c=', c, 'd=', d, '\n')
cls =CopySubTree; print(cls); a = cls(); b = cls(); print('a=', a, 'b=', b)
c = cls.getInstance(); d = cls.getInstance(); print('c=', c, 'd=', d, '\n')
cls =CopyBCSubTree; print(cls); a = cls(); b = cls(); print('a=', a, 'b=', b)
c = cls.getInstance(); d = cls.getInstance(); print('c=', c, 'd=', d, '\n')
print('\nbefore del:\n', _instances); cls.delInstance(); print('after del:\n', _instances);
4. 使用装饰器实现
4.1. 懒汉式
懒汉式就是说“国家”分配对象,在你还未出生的时候就已经被指腹为婚了,这样在你出生的时候就立即拥有了一个对象了,再也不用发愁对象的事儿了,这种不需要自己主动付出就能得到对象的模式被称为懒汉模式。
为了方便,这里我是用装饰器,对需要的类进行装饰,达到了一次定义,以后再处处使用时只需要一行代码的效果。
优点:省心,开始的时候一人分一个对象就好了,很省事。
缺点:构造函数只能是无参的,自己有想法,想传个参数的话是不好传过去的。这是因为在设计时就没考虑你有想法的情况下,别管三七二十一,上来就给你分一个对象。
def Singleton(cls):
print("正在进行装饰类", cls, '哦~')
cls._instance = cls()#需要传参数的话在这里改一下参数列表,但那样的话就不具备如此广泛的通用性了,我们想要的效果是一次定义装饰器,以后对所有的类都适用。
print('类', cls, '装饰完毕,并且我们还给它分配了一个对象', cls._instance, '\n')
def _singleton(): return cls._instance
return _singleton
@Singleton
class A(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
@Singleton
class B(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
if __name__ == '__main__':
print()
a1 = A(); a2 = A(); print(a1, a2)
print()
b1 = B(); b2 = B(); print(b1, b2)
4.2. 饿汉式
饿汉式是指你饿了才给你分一个对象,把对象只分配给那些饥渴难耐大汉们(不分的话可能会出现问题,毕竟大汉如果发起情来还是很骚的,恐怕难以招架)。
优点:节省空间,物尽其用,需要时才给你,如果不需要想单身一辈子的话就不给你分配对象了。
缺点:可能存在线程不安全,饥渴难耐的大汉如果在分配对象的时候一下子多占了多个不同的对象怎么办?
4.2.1. 未加锁版
一般在初始化对象的时候如果不进行IO操作,是没事儿的。(即__init__方法里没有IO操作)
这个版本实现简单,执行速度也快,不用来回上锁了。加锁版的就是给大汉分对象的时候先把大汉五花大绑地锁起来,这样的话就避免了在分配时他抢占多个对象的可能。分配完了之后再给大汉松下绑、解下锁,这样步骤一多,肯定是比较耗时的。
示例1:装饰时给每个类创建一个_instance属性
def Singleton(cls):
print("正在进行装饰类", cls, '哦~')
cls._instance = None
def _singleton(*args, **kargs):
if cls._instance: return cls._instance
cls._instance = cls(*args, **kargs)
return cls._instance
return _singleton
@Singleton
class A(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
@Singleton
class B(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
if __name__ == '__main__':
print()
a1 = A(); a2 = A(); print(a1, a2)
print()
b1 = B(); b2 = B(); print(b1, b2)
示例2:装饰给创建一个空字典,生成对象时再把这个对象添加到字典里,下次如果查到字典里有所需对象的话就直接返回了,没有的话创建后再返回。
_instance = {}
def Singleton(cls):
print("正在进行装饰类", cls, '哦~')
global _instance
def _singleton(*args, **kargs):
if cls in _instance: return _instance[cls]
_instance[cls] = cls(*args, **kargs)
return _instance[cls]
return _singleton
@Singleton
class A(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
@Singleton
class B(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
if __name__ == '__main__':
print()
a1 = A(); a2 = A(); print(a1, a2)
print()
b1 = B(); b2 = B(); print(b1, b2)
4.2.2. 加锁版
import threading
_lock = threading.Lock()
def Singleton(cls):
print("正在进行装饰类", cls, '哦~')
global _lock
cls._instance = None
def _singleton(*args, **kargs):
if cls._instance: return cls._instance
_lock.acquire()#上锁
cls._instance = cls(*args, **kargs)
_lock.release()#去锁
return cls._instance
return _singleton
@Singleton
class A(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
@Singleton
class B(object):
@classmethod
def __new__(cls, *args, **kwargs):
print('__new__ of ', cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('__init__ of', self)
if __name__ == '__main__':
print()
a1 = A(); a2 = A(); print(a1, a2)
print()
b1 = B(); b2 = B(); print(b1, b2)
Python单例模式的设计与实现【完美版】的更多相关文章
- 设计模式(Python)-单例模式
本系列文章是希望将软件项目中最常见的设计模式用通俗易懂的语言来讲解清楚,并通过Python来实现,每个设计模式都是围绕如下三个问题: 为什么?即为什么要使用这个设计模式,在使用这个模式之前存在什么样的 ...
- 分享《Python 游戏编程快速上手(第3版)》高清中文版PDF+高清英文版PDF+源代码
通过编写一个个小巧.有趣的游戏来学习Python,通过实例来解释编程的原理的方式.14个游戏程序和示例,介绍了Python基础知识.数据类型.函数.流程控制.程序调试.流程图设计.字符串操作.列表和字 ...
- 【转载】Stackless Python并发式编程介绍[已校对版]
Stackless Python并发式编程介绍[已校对版] 作者: Grant Olson 电子邮件: olsongt@verizon.net 日期: 2006-07-07 译者: ...
- 《写给大家看的设计书(第3版)》【PDF】下载
<写给大家看的设计书(第3版)>[PDF]下载链接: https://u253469.ctfile.com/fs/253469-231196355 内容简介 <写给大家看的设计书&g ...
- python 单例模式获取IP代理
python 单例模式获取IP代理 tags:python python单例模式 python获取ip代理 引言:最近在学习python,先说一下我学Python得原因,一个是因为它足够好用,完成同样 ...
- Python 单例模式讲解
Python 单例模式讲解 本节内容: classmethod用途 单例模式方法一 类__new__方法讲解 单例模式方法二 前言: 使用单例方法的好处:对于一个类,多次实例化会产生多个对象,若使用单 ...
- Python实例---利用正则实现计算器[FTL版]
import re # 格式化 def format_str(str): str = str.replace('--', '+') str = str.replace('-+', '-') str = ...
- Axure RP7.0移动互联网产品原型设计 中文pdf扫描版
移动互联网原型设计,简单来说,就是使用建模软件制作基于手机或者平板电脑的App,HTML 5网站的高保真原型.在7.0 之前的版本中,使用Axure RP进行移动互联网的建模也是可以的.比如,对于桌面 ...
- 孤荷凌寒自学python第六十一天在Fedora28版的linux系统上找搭建本地Mongodb数据服务
孤荷凌寒自学python第六十一天在Fedora28版的linux系统上找搭建本地Mongodb数据服务 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第七天.成功在本地搭建 ...
随机推荐
- js+php大文件分片上传
1 背景 用户本地有一份txt或者csv文件,无论是从业务数据库导出.还是其他途径获取,当需要使用蚂蚁的大数据分析工具进行数据加工.挖掘和共创应用的时候,首先要将本地文件上传至ODPS,普通的小文件通 ...
- 支持向量机(四)----序列最小最优化算法SMO
在支持向量机(二)和(三)中,我们均遗留了一个问题未解决,即如何求解原问题的对偶问题: 在支持向量机(二)中对偶问题为: 在支持向量机(三)中的对偶问题为: 对于上述两个对偶问题,我们在支持向量机(三 ...
- NSDate 那点事
转载自:http://my.oschina.net/yongbin45/blog/150114 NSDate对象用来表示一个具体的时间点. NSDate是一个类簇,我们所使用的NSDate对象,都是N ...
- Python爬虫数据保存到MongoDB中
MongoDB是一款由C++语言编写的非关系型数据库,是一个基于分布式文件存储的开源数据库系统,其内容存储方式类似于JSON对象,它的字段值可以是其它文档或数组,但其数据类型只能是String文本型. ...
- C# 防火墙操作之特定程序
将特定程序加入防火墙组,与将特定端口加入防火墙流程类似.详情见“C# 防火墙操作之特定端口”.其主要代码为: /// <summary> /// 允许应用程序通过防火墙 /// </ ...
- an ordered dict within the ordered dict
w http://stackoverflow.com/questions/20166749/how-to-convert-an-ordereddict-into-a-regular-dict-in-p ...
- 自定义控件 - 切换开关:SwitchView
自定义控件一般的几个步骤:1.初始化相关背景图片,布局文件,自定义属性2.设置控件宽高OnMeasure()3.布局或者排版OnLayout()4.绘制控件OnDraw()5.处理触摸事件OnTouc ...
- 《图解 TCP-IP(第 5 版)》
第一章 网络基础知识 计算机网络根据规模可以分为:广域网(WAN: Wide Area Network)和局域网(LAN: Local Area Network) 协议的标准化: 国际标准化组织(IS ...
- 【服务器】一次对Close_Wait 状态故障的排查经历
最近接连听说一台线上服务器总是不响应客户端请求. 登录服务器后查询iis状态,发现应用程序池状态变为已停止. 按经验想,重启后应该就ok,第一次遇到也确实起了作用,当时完全没在意,以为是其他人无意把服 ...
- curl 使用 post 请求,传递 json 参数,下载文件
curl -X POST http://ip:8888/nacos/v1/cs/file/download -H "Accept: application/octet-stream" ...