Python单例模式初探

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

什么是单例模式(singleton)

单例模式是一种对于建立对象行为的设计模式,该模式要求一个类只能建立并存有一个实例化对象。
而为何使用单例模式呢?
单例模式是为了不没必要要的内存浪费,如多个线程调用一个类,但实际并不须要对每一个线程都提供一个该类的实例;一样也是为了防止对资源的多重占用,如多个线程要操做同一个文件对象,那么建立多个句柄是不合理的。python

单例模式的设计逻辑

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

  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++相似,它也不能在类内初始化属性为类的实例,如:ui

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

这段代码是错误的,由于对于python在加载类的时候,把类内语句做为可执行的,而执行t=Test()时,Test类还未定义出来,所以矛盾出错。spa

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=123
str="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 time
import threading

class 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生成一个实例,没法完成单例模式。

相关文章
相关标签/搜索