python设计模式--单例模式
一、什么是单例模式
单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。
许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。
比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,
然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
二、单例模式的应用场景
举一个小例子,在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。也就是说在整个系统运行的过程中,系统只维护一个回收站的实例。这就是一个典型的单例模式运用。
继续说回收站,我们在实际使用中并不存在需要同时打开两个回收站窗口的必要性。假如我每次创建回收站时都需要消耗大量的资源,而每个回收站之间资源是共享的,那么在没有必要多次重复创建该实例的情况下,创建了多个实例,这样做就会给系统造成不必要的负担,造成资源浪费。
再举一个例子,网站的计数器,一般也是采用单例模式实现,如果你存在多个计数器,每一个用户的访问都刷新计数器的值,这样的话你的实计数的值是难以同步的。但是如果采用单例模式实现就不会存在这样的问题,而且还可以避免线程安全问题。同样多线程的线程池的设计一般也是采用单例模式,这是由于线程池需要方便对池中的线程进行控制
同样,对于一些应用程序的日志应用,或者web开发中读取配置文件都适合使用单例模式,如HttpApplication 就是单例的典型应用。
从上述的例子中我们可以总结出适合使用单例模式的场景和优缺点:
适用场景:
- 需要生成唯一序列的环境(特定场景需要唯一对象)
- 需要频繁实例化然后销毁的对象。(从使用频率的角度)
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。(从资源消耗的角度)
- 方便资源相互通信的环境
三、单例模式的优缺点
优点:
- 在内存中只有一个对象,节省内存空间;
- 避免频繁的创建销毁对象,可以提高性能;
- 避免对共享资源的多重占用,简化访问;
- 为整个系统提供一个全局访问点。
缺点:
- 不适用于变化频繁的对象;
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;
- 如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失;
四、创建单例模式的方法
1.不管用什么方式实现单例,其主要逻辑都是判断是否存在已经创建的实例,有就返回已经创建过的实例,没就实例化一个实例并存储起来;
2.单例在单线程模型下,是线程安全的,不管怎么样创建实例,都有且只有一个,而面对多线程任务时,一般的单例代码无法承担多线程任务,当有io延时操作时,会生成id不同的实例,所以如果需要实现多线程单例,那么就在创建实例时增加线程锁;
3.实现单例的方式不止三种,只要你抓住if判断的主要逻辑点,就能实现单例,所以单例的实现方式是多种多样的,这里列举了比较常用的几种;
# 使用模块
# 其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。如果我们真的想要一个单例类,可以考虑这样做:
class Singleton(object):
def foo(self):
pass
singleton = Singleton()
# 将上面的代码保存在文件 mysingleton.py 中,要使用时,直接在其他文件中导入此文件中的对象,这个对象即是单例模式的对象
from mysingleton import singleton
# 使用装饰器
def Singleton(cls):
_instance = {}
def _singleton(*args, **kargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kargs)
return _instance[cls]
return _singleton
@Singleton
class A(object):
a = 1
def __init__(self, x=0):
self.x = x
# a1 = A(2)
# a2 = A(3)
# print(id(a1),id(a2))
# 使用类方法
import time
import threading
class Singleton(object):
_instance_lock = threading.Lock()
def __init__(self):
pass
#因为电脑速率执行很快,所以在第一个线程返回Singleton._instance之前都执行了
#Singleton._instance = Singleton(*args, **kwargs)语句
#所以多线程模式下会创建不同实例,但是后边在执行会保存最后一个执行的实例
@classmethod
def instance(cls, *args, **kwargs):
if not hasattr(Singleton, "_instance"):
print("想要创建实例")
with Singleton._instance_lock:
if not hasattr(Singleton, "_instance"):
print("要开始创建了")
cls._instance = Singleton(*args, **kwargs)
return cls._instance
#四、使用__new__
'''
通过上面例子,我们可以知道,当我们实现单例时,为了保证线程安全需要在内部加入锁
我们知道,当我们实例化一个对象时,是先执行了类的__new__方法
(我们没写时,默认调用object.__new__),实例化对象;
然后再执行类的__init__方法,对这个对象进行初始化,所有我们可以基于这个,实现单例模式
'''
# class Singleton:
# def __new__(cls, *args, **kwargs):
# if not hasattr(cls,'_instances'):
# cls._instances = super().__new__(cls)
# return cls._instances
#
# def __call__(self, *args, **kwargs):
# print("我是Singleton")
class MyClass():
def __init__(self,a):
print(self._instances)
self.a = a
def __new__(cls, *args, **kwargs):
#返回类的实例,有因为cls._instances = super().__new__(cls)
#是父类的实例,所以可以直接返回cls._instances
if not hasattr(cls,'_instances'):
cls._instances = super().__new__(cls)
print(cls._instances)
return cls._instances
def __call__(self, *args, **kwargs):
#实现该方法称为可调用对象
print('我被调用了')
# a = MyClass(10)
# a()
# b = MyClass(20)
# print(a.a)
# print(b.a)
# print(id(a),id(b))
# print(hasattr(a.__new__,'__call__'))
# print(hasattr(a,'__call__'))
# print(hasattr(a.a,'__call__'))
#五、使用元类
import threading
#__new__在创建类只执行一次,所以使用__call__
#因为Foo()其实算是SingletonType的实例,所以会自动执行元类的__call__方法
#obj1 = Foo('name1'), cls()调用会调用__call__方法,而cls()为类的实例,所以__call__应该返回类的实例
#元类中有一个特殊的方法__call__,这个方法会截断类的__new__和__init__方法,阻止其执行
#__call__ 应该返回实例,和类的__new__方法返回的一样(type会调用类的__new__和__init__,super()则不会)。
class SingletonType(type):
def __new__(cls, name, bases, attrs):
print("metaclass __new__")
return type.__new__(cls, name, bases, attrs)
_instance_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
print('Metaclass __call__')
print(cls, *args, **kwargs)
if not hasattr(cls, "_instance"):
with SingletonType._instance_lock:
if not hasattr(cls, "_instance"):
#super已经明确子类,不用加cls,而type直接使用元类创建不明确cls,所以需要加
cls._instance = super().__call__(*args, **kwargs)
# cls._instance = type.__call__(cls,*args, **kwargs)
return cls._instance
# 不能使用__new__函数实现单例模式,因为返回的只是类而不是实例,类在创建一次之后已经存在内存里,所以第二次创建相同的实例时不会再经过元类对类进行创建,所以需要使用__call__,而__call__所拿到的cls代表着当前类
class Foo(metaclass=SingletonType):
def __init__(self,name):
print('cls __init__')
self.name = name
def __new__(cls, *args, **kwargs):
print('cls __new__')
return super().__new__(cls)
obj1 = Foo('name1')
# obj2 = Foo('name')
# # print(obj1,obj2)
# print(obj1.name)
#
# a = [1,2,3]