在前一篇blog中咱们提到了logging模块中的root logger是单例模式的,所以紧接着咱们就来摸索一下单例模式在python中的实现。

什么是单例模式(singleton)

单例模式是一种对于创建对象行为的设计模式,该模式要求一个类只能创立并存有一个实例化对象。
而为什么应用单例模式呢?
单例模式是为了防止不必要的内存节约,如多个线程调用一个类,但理论并不需要对每个线程都提供一个该类的实例;同样也是为了避免对资源的多重占用,如多个线程要操作同一个文件对象,那么创立多个句柄是不合理的。

单例模式的设计逻辑

那么如何设计一个单例模式呢?个别的有两个重要的设计逻辑:

  1. 饿汉模式:在类加载时或者至多在类被应用前创立一个单例实例。即不论前面有没有应用该类都创立一个实例。
  2. 懒汉模式:在第一次须要应用类的时候创立一个单例实例。

对于java与c++都有对应的饿汉模式与懒汉模式的实现,这里介绍一下它们的简略实现(不思考线程平安的状况)。

Java

//java饿汉模式public class Singleton {    //间接在类中定义了instance作为Singleton实例,    //即在类加载时就创立出一个单例    private static Singleton instance = new Singleton();    //暗藏构造函数    private Singleton(){}    //裸露惟一能够失去类对象实例的接口getInstance动态函数    public static Singleton getInstance(){        return instance;    }}//java懒汉模式public class Singleton {    //暗藏构造函数    private Singleton() {}      //把instance初始化为null    private static Singleton instance = null;      //裸露惟一能够失去类对象实例的接口getInstance动态函数    public static Singleton getInstance() {        //第一次被调用时,instance是null,所以if判断通过        if (instance == null) {            //失去单例            instance = new Singleton();        }        //之后再调用该接口只会返回以后存在的单例        return instance;    }}

C++

//c++饿汉模式class Singleon{private:    //暗藏构造函数接口    Singleon(){};    //C++中无奈在类定义中赋值,const static的润饰办法也只能用于整型    //定义一个动态类成员,不属于任何一个实例    static Singleon* instance;public:         static Singleon* GetSingleon()    {        return instance;    }    static Singleon* Destroy()    {        delete instance;        instance = NULL;    }};//留神这里,对于c++类中的静态数据成员,不管它拜访限度//是private还是public的,它的初始化都是以如下的模式//(类型名)(类名)::(静态数据成员名)=(value);//而且这里的new Singleton(),即便咱们曾经把构造函数//的拜访限度私有化了,在对静态数据成员初始化的时候//还是能够间接应用,之后在其它任何中央应用私有化的//构造函数接口都是非法的Singleon* Singleon::instance = new Singleton() ;//c++懒汉模式class Singleon{private:    //同样暗藏构造函数接口    Singleon(){};    static Singleon*instance;public:    //裸露的接口    static Singleon* GetSingleon()    {        //判断静态数据成员instance的值        if (instance==NULL)        {            instance = new Singleon();        }        return instance;    }    static Singleon* Destroy()    {        delete instance;        instance = NULL;    }};Singleon* Singleon::instance =  NULL;

能够看出java间接在类内就能够初始化动态成员,所以在加载的时候就能够依照饿汉模式的设计间接生成一个类的单例,而c++不能在类内初始化静态数据成员为类实例,所以还须要在类定义外对其初始化,来造成单例设计模式。至于python,和c++相似,它也不能在类内初始化属性为类的实例,如:

class Test:    t=Test()    .....

这段代码是谬误的,因为对于python在加载类的时候,把类内语句作为可执行的,而执行t=Test()时,Test类还未定义进去,因而矛盾出错。

python类内namespace和scope的解析

在python中namespace和scope其实一体两面的概念,namespace着眼于name与object之间的映射关系,确保name之间不存在conflict,python中存在三种namespace:

  1. local namespace(函数内,类内)
  2. global namespace
  3. builtin namespace

而scope则形容的是变量(directly access)寻找的问题,它为此定义了一个hierarchy:LEGB(Local->Enclosing->Global->Builtin)。

对于python中类内的namespace是失常的,属于local namespace,然而对于类内的scope,它是LEGB这个hierarchy中的一个非凡存在。上面咱们借由一段代码解释一下:

var=123str="love you!"class Test:    var=19    print(str)    def fun(self):        print(var)t=Test()t.fun()--------------------------下面这段代码执行后,输入如下:love you!123

可见,类的method间接忽视了类内的scope(当然用类名限定:Test.var也是能够拜访到的,然而咱们当初探讨的LEGB是directly access的问题),间接找到的是global中的变量var,而类内的print(str)也能够找到global的str。那么简略来说:1.python类内scope对类内method不可见,也就是说类内定义的变量或是其余method都不能被某一个method间接援用。2.python类外向外寻找是合乎LEGB规定的。

python单例模式设计

咱们这里只简略介绍一下应用__new__办法进行单例设计的形式,当前若有工夫再进行补充。
首先python没法像java和c++一样把接口暗藏起来,同时也无奈在类定义中初始化类属性为类实例。以此为前提,咱们应用__new__办法来探讨单例模式的设计(new其实相当于半个构造函数,只结构实例不初始化):

#所谓饿汉模式class Singleton:    def __new__(cls):        if not hasattr(Singleton,"instance"):            cls.instance=super().__new__(cls)        return cls.instance#在应用前实例化Singleton()#懒汉模式->能够说所谓的饿汉模式就是在懒汉模式后加了Singleton()class Singleton:    def __new__(cls):        if not hasattr(Singleton,"instance"):            cls.instance=super().__new__(cls)        return cls.instance

看完以上的代码兴许你会感觉有些牵强,感觉python的饿汉模式有些“假”,然而饿汉懒汉其实都是一种设计逻辑罢了,只有实现了相应的逻辑,具体的代码之间的区别并不重要!

线程平安的python单例模式

这里须要对python单例模式的线程平安进行进一步的介绍,因为下面咱们介绍的__new__办法设计单例模式的饿汉懒汉代码差异不大,咱们这里就以懒汉模式进行线程平安的介绍,看代码:

import timeimport threadingclass Singleton:    def __new__(cls):        #假如有两个thread都一次执行到了这个if判断,        #thead1通过,而后继续执行time.sleep(1),        #那么在这个1秒的sleep中,thread2也通过了        #这个if判断,则前面很显然的会创立两个实例        if not hasattr(Singleton,"instance"):            time.sleep(1)            cls.instance=super().__new__(cls)        return cls.instance        针对以上状况,咱们应用线程锁进行改良class Singleton:    lock=threading.lock    def __new__(cls):        #还是雷同的状况,两个thead执行到这个判断,        #thead1先通过,执行下一句with threading.Lock()        #那么便间接占有了锁,之后在thread1的1秒sleep中        #thread2也通过了第一个if判断,而继续执行执行        #with threading.Lock()语句,无奈抢占锁,被阻塞        #当thread1实现1秒的sleep后,并且通过第二个if,        #对cls.instance赋值,退出with context后,thread2        #能力继续执行,1秒sleep之后再进行第二个if判断,        #此时不能通过了,因为thread1曾经创立了一个instance        #那么只好退出with context,再执行return cls.instance        #其实就是返回thread1创立的cls.instance        if not hasattr(Singleton,"instance"):            #threading.Lock()反对context manager protocol            with Singleton.Lock():                 time.sleep(1)                 if not hasattr(Singleton,"instance"):                        cls.instance=super().__new__(cls)            return cls.instance            

以上的单例模式线程平安实现办法叫做:双重检测机制。两个if判断,其中第一个if判断是为了避免每个thread都要进行抢占锁而后执行对应代码,会很浪费时间;而第二个if判断则更加要害,如果有多个thread都通过了第一个if判断,进入锁的抢占,如果没有第二个if判断,那么还是会每个thread生成一个实例,无奈实现单例模式。