相关代码已经上传至Github:Python_Development_Interview,大家可以收藏专题-Python的设计模式:解密+实战,之后会持续更新相关的设计模式。

1. 认识单例模式

  • 认识单例模式
  • 1 单例模式含义
  • 2 单例模式优点
  • 3 单例模式缺点
  • 4 单例模式应用

2. Python实现单例模式

  • Python实现单例模式
  • 1 多种实现方法
  • 2 实例分析

3. 总结

  • 总结

认识单例模式

1.1 单例模式含义

例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。

1.2 单例模式优点

单例模式的优点: 1、由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间; 2、全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用; 3、单例可长驻内存,减少系统开销。

1.3 单例模式缺点

单例模式的缺点 1、单例模式的扩展是比较困难的; 2、赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到); 3、单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试; 4、单例模式在某种情况下会导致“资源瓶颈”。

1.4 单例模式应用

单例模式的应用举例: 1、生成全局惟一的序列号; 2、访问全局复用的惟一资源,如磁盘、总线等; 3、单个对象占用的资源过多,如数据库等; 4、系统全局统一管理,如Windows下的Task Manager; 5、网站计数器。

Python实现单例模式

2.1 多种实现方法

2.1.1.使用模块

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

singleton_by_module.py

class Singleton(object):def foo(self):pass
singleton = Singleton()

将上面的代码保存在文件singleton_by_module.py中,要使用时,直接在其他文件中导入此文件中的对象,这个对象即是单例模式的对象test_singleton_by_module.py

from singleton_by_module import Singletont = Singleton()

这样我们一旦调用到singleton_by_module.py就会产生一个singleton_by_module.pyc,以后我们每次调用都会直接引用这里面的代码。

2.1.2.使用装饰器

singleton_by_decorator.py

def Singleton(cls):_instance = {}count = 0def _singleton(*args, **kargs):nonlocal countif cls not in _instance:print(f"count: {count}: {cls.__name__} not init")_instance[cls] = cls(*args, **kargs)else:print(f"count: {count}: {cls.__name__} alreay init")count+=1return _instance[cls]return _singleton@Singleton
class A(object):a = 1def __init__(self, x=0):self.x = xa1 = A(2)
a2 = A(3)print(f"a1 id: {id(a1)}, a1 value: {a1.x}")
print(f"a2 id: {id(a2)}, a2 value: {a2.x}")### output
count: 0: A not init
count: 1: A alreay init
a1 id: 140536039677232, a1 value: 2
a2 id: 140536039677232, a2 value: 2

根据上面的运行情况,我们可以发现,当a1被创建后调用的是正常的产生实例的过程,当a2被创建的时候,由于之前实例已经被存储下来,所以直接引用了a1的实例,所以他们的id是一样的,也就是他们引用了同一个内存实例。

2.1.3.使用类

singleton_by_class.py

class Singleton:def __init__(self):pass@classmethoddef instance(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instancea1 = Singleton.instance()
a2 = Singleton.instance()print(f"a1 id: {id(a1)}")
print(f"a2 id: {id(a2)}")### output
a1 id: 140419818871776
a2 id: 140419818871776

一般情况,大家以为这样就完成了单例模式,但是这样当使用多线程时会存在问题

singleton_by_class_mutli_threading.py

class Singleton(object):def __init__(self):pass@classmethoddef instance(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instanceimport threadingdef task(arg):obj = Singleton.instance()print(obj)for i in range(10):t = threading.Thread(target=task,args=[i,])t.start()

程序执行后,打印结果如下:

<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>

看起来也没有问题,那是因为执行速度过快,如果在init方法中有一些IO操作,就会发现问题了,下面我们通过time.sleep模拟

我们在上面init方法中加入以下代码:

singleton_by_class_mutli_threading_sleep.py

def __init__(self):import timetime.sleep(1)

重新执行程序后,结果如下

<__main__.Singleton object at 0x034A3410>
<__main__.Singleton object at 0x034BB990>
<__main__.Singleton object at 0x034BB910>
<__main__.Singleton object at 0x034ADED0>
<__main__.Singleton object at 0x034E6BD0>
<__main__.Singleton object at 0x034E6C10>
<__main__.Singleton object at 0x034E6B90>
<__main__.Singleton object at 0x034BBA30>
<__main__.Singleton object at 0x034F6B90>
<__main__.Singleton object at 0x034E6A90>

问题出现了!按照以上方式创建的单例,无法支持多线程

解决办法:加锁!未加锁部分并发执行,加锁部分串行执行,速度降低,但是保证了数据安全

singleton_by_class_mutli_threading_lock.py

import time
import threading
class Singleton:_instance_lock = threading.Lock()def __init__(self):time.sleep(1)@classmethoddef instance(cls, *args, **kwargs):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instancedef task(arg):obj = Singleton.instance()print(obj)
for i in range(10):t = threading.Thread(target=task,args=[i,])t.start()
time.sleep(20)
obj = Singleton.instance()
print(obj)

打印结果如下:

<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>

这样就差不多了,但是还是有一点小问题,就是当程序执行时,执行了time.sleep(20)后,下面实例化对象时,此时已经是单例模式了,但我们还是加了锁,这样不太好,再进行一些优化,把intance方法,改成下面的这样就行:

@classmethoddef instance(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instance

这样,一个可以支持多线程的单例模式就完成了

singleton_by_class_mutli_threading_safe.py

import time
import threading
class Singleton:_instance_lock = threading.Lock()def __init__(self):time.sleep(1)@classmethoddef instance(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instancedef task(arg):obj = Singleton.instance()print(obj)
for i in range(10):t = threading.Thread(target=task,args=[i,])t.start()
time.sleep(20)
obj = Singleton.instance()
print(obj)完整代码

这种方式实现的单例模式,使用时会有限制,以后实例化必须通过 obj = Singleton.instance()

如果用 obj=Singleton() ,这种方式得到的不是单例

2.1.4基于new方法实现(推荐使用,方便)

通过上面例子,我们可以知道,当我们实现单例时,为了保证线程安全需要在内部加入锁

我们知道,当我们实例化一个对象时,是先执行了类的new方法(我们没写时,默认调用type.new),实例化对象;然后再执行类的init方法,对这个对象进行初始化,所有我们可以基于这个,实现单例模式

singleton_by_new.py

import threading
class Singleton:_instance_lock = threading.Lock()def __init__(self):passdef __new__(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = super(Singleton,cls).__new__(cls,*args, **kwargs)return Singleton._instanceobj1 = Singleton()
obj2 = Singleton()
print(obj1,obj2)def task(arg):obj = Singleton()print(obj)for i in range(10):t = threading.Thread(target=task,args=[i,])t.start()

打印结果如下:

<__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>

采用这种方式的单例模式,以后实例化对象时,和平时实例化对象的方法一样 obj = Singleton()

2.1.5.基于metaclass方式实现

相关知识

""" 1.类由type创建,创建类时,type的init方法自动执行,类() 执行type的call方法(类的new方法,类的init方法) 2.对象由类创建,创建对象时,类的init方法自动执行,对象()执行类的call方法 """

class Foo:def __init__(self):passdef __call__(self, *args, **kwargs):passobj = Foo()
# 执行type的 __call__ 方法,调用 Foo类(是type的对象)的 __new__方法,用于创建对象,然后调用 Foo类(是type的对象)的 __init__方法,用于对对象初始化。obj()    # 执行Foo的 __call__ 方法

元类的使用metaclass_ex.py

class SingletonType(type):def __init__(self,*args,**kwargs):super(SingletonType,self).__init__(*args,**kwargs)def __call__(cls, *args, **kwargs): # 这里的cls,即Foo类print('cls',cls)obj = cls.__new__(cls,*args, **kwargs)cls.__init__(obj,*args, **kwargs) # Foo.__init__(obj)return objclass Foo(metaclass=SingletonType): # 指定创建Foo的type为SingletonTypedef __init__(self,name):self.name = namedef __new__(cls, *args, **kwargs):return object.__new__(cls)obj = Foo('xx')

实现单例模式

singleton_by_metaclass.py

import threadingclass SingletonType(type):_instance_lock = threading.Lock()def __call__(cls, *args, **kwargs):if not hasattr(cls, "_instance"):with SingletonType._instance_lock:if not hasattr(cls, "_instance"):cls._instance = super(SingletonType,cls).__call__(*args, **kwargs)return cls._instanceclass Foo(metaclass=SingletonType):def __init__(self,name):self.name = nameobj1 = Foo('name')
obj2 = Foo('name')
print(obj1,obj2)

2.2 实例分析

总线是计算机各种功能部件或者设备之间传送数据、控制信号等信息的公共通信解决方案之一。现假设有如下场景:某中央处理器(CPU)通过某种协议总线与一个信号灯相连,信号灯有64种颜色可以设置,中央处理器上运行着三个线程,都可以对这个信号灯进行控制,并且可以独立设置该信号灯的颜色。抽象掉协议细节(用打印表示),如何实现线程对信号等的控制逻辑。 加线程锁进行控制,无疑是最先想到的方法,但各个线程对锁的控制,无疑加大了模块之间的耦合。下面,我们就用设计模式中的单例模式,来解决这个问题。

代码如下:

import threading
import time
#这里使用方法__new__来实现单例模式
class Singleton(object):#抽象单例def __new__(cls, *args, **kw):if not hasattr(cls, '_instance'):orig = super(Singleton, cls)cls._instance = orig.__new__(cls, *args, **kw)return cls._instance
#总线
class Bus(Singleton):lock = threading.RLock()def sendData(self,data):self.lock.acquire()time.sleep(3)print "Sending Signal Data...",dataself.lock.release()
#线程对象,为更加说明单例的含义,这里将Bus对象实例化写在了run里
class VisitEntity(threading.Thread):my_bus=""name=""def getName(self):return self.namedef setName(self, name):self.name=namedef run(self):self.my_bus=Bus()self.my_bus.sendData(self.name)if  __name__=="__main__":for i in range(3):print "Entity %d begin to run..."%imy_entity=VisitEntity()my_entity.setName("Entity_"+str(i))my_entity.start()

运行结果如下: Entity 0 begin to run... Entity 1 begin to run... Entity 2 begin to run... Sending Signal Data... Entity_0 Sending Signal Data... Entity_1 Sending Signal Data... Entity_2 在程序运行过程中,三个线程同时运行(运行结果的前三行先很快打印出来),而后分别占用总线资源(后三行每隔3秒打印一行)。虽然看上去总线Bus被实例化了三次,但实际上在内存里只有一个实例。

总结

因为单例模式在设计模式中算是最基础且最简单的一个模式,因此在一般初级面试的时候,面试官都会通过这个问题来考察,一个很重要的原因是单例模式实现方法多种且优化的方式也有很多,所以也很能考察应聘者的水平,所以,大家要好好学这个最基础的设计模式啊!另外,在Java中单例模式常说的饱汉饿汉模式,其实和Python中的利用__new__和利用class来创建是一样的,也就是在什么时候创建实例的区别。

以下哪个选项不是单例模式的优点_深度解密Python单例模式相关推荐

  1. print python 如何加锁_深度解密Python单例模式

    相关代码已经上传至Github:Python_Development_Interview,大家可以收藏专题-Python的设计模式:解密+实战,之后会持续更新相关的设计模式. 1. 认识单例模式 认识 ...

  2. java单例模式 三种_三种java单例模式概述

    在java语言的应用程序中,一个类Class只有一个实例存在,这是由java单例模式实现的.Java单例模式是一种常用的软件设计模式,java单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种.下 ...

  3. 以下哪个选项不是单例模式的优点_设计模式--单例

    概述 单例模式(SingletonPattern),保证一个类仅有一个实例,并提供一个访问它的全局访问点. 单例模式有 3 个特点: 单例类只有一个实例对象: 该单例对象必须由单例类自行创建: 单例类 ...

  4. 写一个饿汉单例模式的例子_你写的单例模式,能防止反序列化和反射吗?

    推荐学习 "23种设计模式知识要点"都没读通过,还有脸说摸不清搞不懂? 玩转JAVA筑基之Netty.并发编程与设计模式,打好基础备战春招 前言 说起单例模式,相信大家都不会陌生. ...

  5. 关于python的列表以下选项中描述错误的是_以下关于Python列表的描述中,错误的是______...

    以下关于Python列表的描述中,错误的是______ 答:列表的长度和内容都可以改变,但元素类型必须相同 Which of the following statements is NOT true ...

  6. 以下属于单例模式的优点的是_单例模式的优缺点和使用场景

    能有一个文件系统. 10. HttpApplication 也是单位例的典型应用.熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpM ...

  7. 命令模式的优点?_一篇目录复习完设计模式

    设计模式分类: 创建型设计模式 创建型设计模式的主要关注点是"怎样创建对象?",它的主要特点是"将对象的创建与使用分离".这样可以降低系统的耦合度,使用者不需要 ...

  8. java 反射 单列_轻松掌握Java单例模式

    单例模式是23中设计模式中最简单的设计模式,在企业开发中也应用的特别多.单例模式的优点是:项目中有且仅有一个实例. 特点:构造器私有化,对象私有化,只提供一个对外访问的接口. 应用场景: 1.系统需要 ...

  9. 单例模式应用场景_【简易设计模式04】单例模式

    总第56篇 在上篇中,我们对整个系统的设计应遵循的六大设计原则进行了系统性的梳理和讲解.在开发过程中,我们应尽量按照这些设计原则去做,当然也不能生搬硬套,要适当权衡,根据自己的项目情况.项目场景去考量 ...

最新文章

  1. Linux之父警告全球程序员:我刚发布的5.12内核有bug,你们千万别用
  2. 深度优先搜索 和问题 简单函数递归 “加 还是不加”
  3. 中国聚氨酯胶粘剂行业现状研究分析及市场前景预测报告(2022年)
  4. JSP与Servelt的区别
  5. 欧拉函数 euler
  6. 【汇总】pytorch里的一些函数
  7. 算法岗面试整理 | 腾讯、字节、美团、阿里
  8. 计算机识别不了佳能打印机,佳能打印机不能打印如何解决 佳能打印机不能打印解决方法【介绍】...
  9. 搜狗拼音输入法4.2_巧用搜狗快速输入特殊字符与表情字符画
  10. 燃料电池系统建模(未完待续)
  11. OJ密码岛 1243.谢尔宾斯基三角形
  12. PROFINET 链路简述
  13. 【计算机网络 (谢希仁) 习题题解】第5章 运输层 (5)——TCP的运输连接管理
  14. Excel查找、替换与定位
  15. 舒淇放下黎明战胜抑郁 自称没责任感且不会结婚q1h
  16. macOS Monterey 12.3 (21E230) 虚拟机 IOS 镜像
  17. 美团智能配送系统的运筹优化实战-笔记
  18. h5课件制作_H5课件——H5页面在教学上的妙用
  19. IE浏览器上不了网的原因
  20. GPS北斗授时服务,NTP让网络时钟同步更精确

热门文章

  1. ABAP的一个新关键字 - How does FROM lookup_tab work
  2. QHD DDIC is implemented via HANA
  3. CRM product ID format相关配置
  4. Fiori应用deploy到云上后在Chrome开发者工具里Source标签页的外观
  5. mysql注入语句解释,MYSQL注入语句实用精解
  6. 计算机办公高级试题,高级办公软件试题及解答
  7. mysql中存储gis数据类型_mysql 中的数据类型
  8. php上传文件到七牛云源码,laravel上传文件到七牛云存储
  9. python os.system关闭log_又到牛市!带你学习一个python强大证券数据分析工具
  10. 201609计算机控制技术作业三,计算机控制技术大作业2015..doc