Python实现Singleton模式的几种方式

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

使用python实现设计模式中的单例模式。单例模式是一种比较经常使用的设计模式,其实现和使用场景断定都是相对容易的。本文将简要介绍一下python中实现单例模式的几种常见方式和原理。一方面能够加深对python的理解,另外一方面能够更加深刻的了解该模式,以便实际工做中能更加灵活的使用单例设计模式。python

本文将介绍常见的实现单例模式的几种方式,这里暂不考虑多线程的状况。设计模式

为了准备该篇博文,以前写了几篇相关的文章依次完整的介绍了相关的概念,下面会在须要的时候给出连接。多线程

装饰器做为python实现单例模式的一种经常使用方法,先简单了解一下其概念。闭包

1.装饰器app

装饰器(Decorator)能够用做对函数以及类进行二次包裹或者封装,使用方式@wrapper。ide

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

上面这两种方式对函数的定义在语法上是等价的。固然对于类也有一样的用法,类能够做为装饰器也能够做为被装饰对象。惟一的区别就是通过包裹的类可能不在是一个类,而是一个类的对象或者一个函数,这取决于装饰器返回的值。函数

通过Decorator装饰的类或者函数本质上已经再也不是原来的类或者函数了。可是,实际上在包裹以后获得的新对象仍然拥有被包裹对象的特性(这句是否是废话:-))。学习

在python中咱们常常只须要实现一个装饰器,而后使用该装饰器做用于只能有惟一一个实例的类。这样只须要实现一个这样的装饰器,即可以做用于任何一个想要惟一实例的类。线程

2.闭包方式设计

闭包的应用不少,单例模式则是其应用之一。先看代码:

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

这个实现单例模式的方式将原来类的定义隐藏在闭包函数中,经过闭包函数及其中引用的自由变量来控制类对象的生成。因为惟一的实例存放在自由变量中,并且自由变量是没法直接在脚本层进行访问的。这种方式很是隐蔽的保护实例不被修改,所以很适合用于单例模式。

这种方式简单明了,很容易实现。可是若是不了解闭包实现过程和变量的绑定等概念可能会不明白其实现的过程。建议参考一下个人另外一篇博文:理解python闭包概念。

这里一个颇有趣的地方是为何要使用instances = {}这样一个变量?可不能够不用字典,使用instance = None?若是singleton做为装饰器被多个不一样的类使用,那么instance中会存在几个不一样的实例么?

有时间能够思考一下这几个问题,答案也能够在我写的闭包相关的博文中找到。

3.元类方式

所谓单例模式,即咱们须要控制类实例的生成过程,而且保证全局只可能存在一个惟一的实例。既然须要在建立类的对象过程当中作些什么,应该很容易想到元类。

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

这个例子中咱们使用元类Singleton替代默认使用type方式建立类my_cls。能够将类my_cls看作是元类Singleton的一个对象,当咱们使用my_cls(...)的方式建立类my_cls的对象时,其实是在调用元类Singleton的对象my_cls。

对象能够以函数的方式被调用,那么要求类中定义__call__函数。不过此处被调用的是类,所以咱们在元类中定义函数__call__来控制类my_cls对象建立的惟一性。

这种方式的弊端之一就是类惟一的对象被存放在类的一个静态数据成员中,外部能够经过class_name._instance的方式修改甚至删除这个实例(该例中my_cls._instance = None彻底合法)。

4.类做为装饰器之__call__方式

不只函数能够做为装饰器,类也能够做为装饰器。

下面简单的介绍一下使用类做为装饰器实现单例模式的另外一种方式。

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

函数做为装饰器返回的是一个函数,函数被调用过程当中其实是间接地调用其内部包裹的被装饰的对象。

类做为装饰器要想达到相同的效果只须要将类的对象返回,而且其对象是能够调用的。这是上面这个例子表达的一个核心思想。

这种方式写法不少,也很灵活,其思想基本上就是对被包裹对象的调用实际上调用的是类对象的__call__函数,该函数其实是对被装饰对象的一次封装。

5.类自己实现方式

上面的例子中咱们都是使用的装饰器或者元类的方式间接的经过控制类对象生成的方式来保证对象的惟一性,那么有没有办法直接在类中经过某种方式保证类对象的惟一性?

答案是确定的。参考我以前写的一篇介绍元类的文章,可知生成对象前会调用函数__new__,若是__new__函数返回被建立的对象,那么会自动调用类中定义的__init__函数进行对象的初始化操做。

相信读了上面这句话,应该知道咱们接下来要干什么了?没错,咱们的目标就是__new__。

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

在这个例子中,咱们彻底能够理解为何只会有一个类的对象会被建立。这种方式的定义决定了类自己只能被建立一个对象。

可是这里有一点须要注意,那就是无论建立多少MSC的对象,至始至终只会有一个对象,可是若是每次建立的时候传入的参数都不一样,也就是__init__函数中参数不一样,会致使同一个对象被屡次初始化。

这种方式的弊端显然很明显,那就是该方法只能做用于单个类的定义。不能像上面的装饰器和元类,一次实现,能够处处使用。

那能不能将这个控制类生成过程的结构单独抽象出来呢?并且有没有什么方法能防止同一个对象屡次被__init__初始化。下面咱们看一种能被不一样的类使用的更加抽象的结构。

6.替换__new__方式

咱们定义的类做为一个对象,经过替换其部分属性能够达到控制类对象生成的目的。

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

上面咱们经过替换类的__new__函数和__init__函数的方式,保证被Singleton装饰的类只有一个对象会被原来的__new__和__init__生成和初始化。

这里必需要替换类的__init__函数,并且该函数应该什么都不作。缘由在于替换以后的__new__返回惟一的对象后,会自动调用如今的__init__函数。

原来的__init__函数已经在建立惟一一个对象时被调用过。并且只能被调用一次。

这里返回的并非闭包结构,只是使用装饰器修改了类的部分属性,返回的还是传入的类。可是类的__new__函数引用了Singleton中的local variable _instance。

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

Cell 对象my_cls.__new__.func_closure[0]中存放的即是类my_cls惟一的实例。

固然咱们能够将my_cls惟一的对象做为类的一个静态数据成员放入cls.__dict__中来替代_instance = {},可是显然闭包结构更适合。

7.注意事项

文中借助python语言的类建立对象过程的相关原理,介绍了几种不一样的单例模式实现方式。

为了保留被装饰对象的一些属性,可使用@functools.wraps的方式对返回的闭包进行装饰。

平时建议使用前两种实现方式,也就是闭包方式和元类方式。其余状况多少有点玩弄python语法技巧的一些嫌疑,固然了,做为学习python来讲仍是比较有意义的。 想要学习Python开发的同窗,能够参考成都Python培训班提供的学习大纲;

相关文章
相关标签/搜索