不少初学者喜欢用 全局变量 ,由于这比函数的参数传来传去更容易让人理解。确实在不少场景下用全局变量很方便。不过若是代码规模增大,而且有多个文件的时候,全局变量就会变得比较混乱。你可能不知道在哪一个文件中定义了相同类型甚至重名的全局变量,也不知道这个变量在程序的某个地方被作了怎样的操做。python
所以对于这种状况,有种更好的实现方式:
单例(Singleton)面试
单例是一种 设计模式 ,应用该模式的类只会生成一个实例。算法
单例模式保证了在程序的不一样位置都 能够且仅能够取到同一个对象实例 :若是实例不存在,会建立一个实例;若是已存在就会返回这个实例。由于单例是一个类,因此你也能够为其提供相应的操做方法,以便于对这个实例进行管理。编程
举个例子来讲,好比你开发一款游戏软件,游戏中须要有“场景管理器”这样一种东西,用来管理游戏场景的切换、资源载入、网络链接等等任务。这个管理器须要有多种方法和属性,在代码中不少地方会被调用,且被调用的必须是同一个管理器,不然既容易产生冲突,也会浪费资源。这种状况下,单例模式就是一个很好的实现方法。设计模式
单例模式普遍应用于各类开发场景,对于开发者而言是必须掌握的知识点,同时在不少面试中,也是常见问题。本篇文章总结了目前主流的实现单例模式的方法供读者参考。网络
但愿看过此文的同窗,在之后被面到此问题时,能直接皮一下面试官,“我会 4 种单例模式实现,你想听哪种?”函数
如下是实现方法索引:学习
如下是实现代码:区块链
def singleton(cls): _instance = {} def inner(): if cls not in _instance: _instance[cls] = cls() return _instance[cls] return inner @singleton class Cls(object): def __init__(self): pass cls1 = Cls() cls2 = Cls() print(id(cls1) == id(cls2))
输出结果:搜索引擎
True
在 Python 中,id 关键字可用来查看对象在内存中的存放位置,这里 cls1 和 cls2 的 id 值相同,说明他们指向了同一个对象。
关于装饰器的知识,有不明白的同窗能够查看以前的文章 【编程课堂】装饰器浅析 或者使用搜索引擎再学习一遍。代码中比较巧妙的一点是:
_instance = {}
使用不可变的 类地址 做为键,其实例做为值,每次创造实例时,首先查看该类是否存在实例,存在的话直接返回该实例便可,不然新建一个实例并存放在字典中。
代码:
class Singleton(object): def __init__(self, cls): self._cls = cls self._instance = {} def __call__(self): if self._cls not in self._instance: self._instance[self._cls] = self._cls() return self._instance[self._cls] @Singleton class Cls2(object): def __init__(self): pass cls1 = Cls2() cls2 = Cls2() print(id(cls1) == id(cls2))
同时,因为是面对对象的,这里还能够这么用
class Cls3(): pass Cls3 = Singleton(Cls3) cls3 = Cls3() cls4 = Cls3() print(id(cls3) == id(cls4))
使用 类装饰器实现单例的原理和 函数装饰器 实现的原理类似,理解了上文,再理解这里应该不难。
在接着说另外两种方法以前,须要了解在 Python 中一个类和一个实例是经过哪些方法以怎样的顺序被创造的。
简单来讲, 元类 ( metaclass ) 能够经过方法 metaclass 创造了 类(class) ,而 类(class) 经过方法 new 创造了 实例(instance) 。
在单例模式应用中,在创造类的过程当中或者创造实例的过程当中稍加控制达到最后产生的实例都是一个对象的目的。
本文主讲单例模式,因此对这个 topic 只会点到为止,有感兴趣的同窗能够在网上搜索相关内容,几篇参考文章:
https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python
http://howto.lintel.in/python-__new__-magic-method-explained/
https://stackoverflow.com/questions/674304/why-is-init-always-called-after-new
使用 new 方法在创造实例时进行干预,达到实现单例模式的目的。
class Single(object): _instance = None def __new__(cls, *args, **kw): if cls._instance is None: cls._instance = object.__new__(cls, *args, **kw) return cls._instance def __init__(self): pass single1 = Single() single2 = Single() print(id(single1) == id(single2))
在理解到 new 的应用后,理解单例就不难了,这里使用了
_instance = None
来存放实例,若是 _instance 为 None,则新建实例,不然直接返回 _instance 存放的实例。
一样,咱们在类的建立时进行干预,从而达到实现单例的目的。
在实现单例以前,须要了解使用 type 创造类的方法,代码以下:
def func(self): print("do sth") Klass = type("Klass", (), {"func": func}) c = Klass() c.func()
以上,咱们使用 type 创造了一个类出来。这里的知识是 mataclass 实现单例的基础。
class Singleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) return cls._instances[cls] class Cls4(metaclass=Singleton): pass cls1 = Cls4() cls2 = Cls4() print(id(cls1) == id(cls2))
这里,咱们将 metaclass 指向 Singleton 类,让 Singleton 中的 type 来创造新的 Cls4 实例。
本文虽然是讲单例模式,但在实现单例模式的过程当中,涉及到了蛮多高级 Python 语法,包括装饰器、元类、new、type 甚至 super 等等。对于新手同窗可能难以理解,其实在工程项目中并不须要你掌握的面面俱到,掌握其中一种,剩下的做为了解便可。
by 周鑫鑫
关于更多的设计模式,给初学者推荐《 Head First 设计模式 》(Head First Design Patterns),此书浅显易懂,在 Head First 系列书籍里面也算是很好的一本。
咱们的资源网盘里有电子版,获取地址请在公众号( Crossin的编程教室 )里回复关键字: 资源
════
其余文章及回答:
如何自学Python | 新手引导 | 精选Python问答 | Python单词表 | 区块链 | 人工智能 | 双11 | 嘻哈 | 爬虫 | 排序算法
欢迎搜索及关注: Crossin的编程教室