美文网首页python学习
python 单例模式

python 单例模式

作者: snailpy | 来源:发表于2018-08-19 12:21 被阅读155次

    单例模式 Singleton

    网上搜索python 单例模式,实现方式大致有一下几种方式:

    • 以模块导入的方式
    • 使用__new__
    • 使用 装饰器(decorator)
    • 使用 元类 (metaclass)
    • 使用 面向对象的常用方式(类方法)
    • 使用 变量名覆盖

    推荐方式:

    以《剑指offer》的Singleton的实现顺序来讲解,直接上我认为python实现单例模式的最佳方式 即 网传的 __new__方式,直接上代码:

    class Singleton(object):
        __instance = None
        def __new__(cls, *args, **kwargs):
            if not Singleton.__instance:
                Singleton.__instance = super().__new__(cls)
            return Singleton.__instance
    
    #测试
    for i in range(5):
        s = Singleton()
        print(s)
    输出:
    <__main__.Singleton object at 0x000000000B685278>
    <__main__.Singleton object at 0x000000000B685278>
    <__main__.Singleton object at 0x000000000B685278>
    <__main__.Singleton object at 0x000000000B685278>
    <__main__.Singleton object at 0x000000000B685278>
    

    可以看到5次实例化的 Singleton 对象均为同一个变量,但是该方法只适用于单线程模式,多线程模式下多个线程可能同时进入 if not Singleton.__instance: 中 此时就会返回不同的 Singleton 对象,测试:

    #测试:
    import time
    import threading
    
    class Singleton(object):
        __instance = None
        def __new__(cls, *args, **kwargs):
            if not Singleton.__instance:
                time.sleep(2) #为了模仿有大量线程运行时,多个线程可能同时进入,特此暂定2秒钟
                Singleton.__instance = super().__new__(cls)
            return Singleton.__instance
    
    def test():
        s = Singleton()
        print('------%s\n' % s)
    
    def main():
        t = {}
        for i in range(5):
            t[i] = threading.Thread(target=test)
            t[i].start()
        for i in range(5):
            t[i].join()
    main()
    
    输出:
    ------<__main__.Singleton object at 0x000000000B789080>
    ------<__main__.Singleton object at 0x000000000B7895C0>
    ------<__main__.Singleton object at 0x000000000B7896A0>
    ------<__main__.Singleton object at 0x000000000B645F60>
    ------<__main__.Singleton object at 0x000000000B7896A0>
    

    可见5次实例化的 Singleton 对象确实在多线程的情况下并没有实现正真的单例情况。那怎么办,自然有方法啦,继续按照《剑指offer》的思路走,在线程要抢占的共享资源处上锁。如下:

    class Singleton(object):
        __lock = threading.Lock() #生成一个锁对象
        __instance = None
        def __new__(cls, *args, **kwargs):
            Singleton.__lock.acquire()
            if not Singleton.__instance:
                time.sleep(2) #为了模仿有大量线程运行时,多个线程可能同时进入,特此暂定2秒钟
                Singleton.__instance = super().__new__(cls)
            Singleton.__lock.release()
            return Singleton.__instance
    #测试
    输出:
    ------<__main__.Singleton object at 0x000000000B645AC8>
    ------<__main__.Singleton object at 0x000000000B645AC8>
    ------<__main__.Singleton object at 0x000000000B645AC8>
    ------<__main__.Singleton object at 0x000000000B645AC8>
    ------<__main__.Singleton object at 0x000000000B645AC8>
    

    如上,多线程情况下,每一次获取的 Singleton 对象都是同一变量,此处注意__lock 一定要在类中唯一,否则下面的上锁与释放锁过程会失效,或者出错。按照网上以及《剑指offer》的流程还要继续优化,即:Singleton.__lock.acquire() 上锁,释放锁的过程会耗时比较严重,所以需要在上锁之前再做判断,如下:

    ...
    class Singleton(object):
        __lock = threading.Lock()
        __instance = None
        def __new__(cls, *args, **kwargs):
            if not Singleton.__instance:
                Singleton.__lock.acquire()
                if not Singleton.__instance:
                    time.sleep(2) #为了模仿有大量线程运行时,多个线程可能同时进入,特此暂定2秒钟
                    Singleton.__instance = super().__new__(cls)
                Singleton.__lock.release()
            return Singleton.__instance
    ...
    

    这样只有第一个线程会进入if中,即只有一个线程会有 上锁及释放锁的过程,以后的线程会直接返回现有的__instance实例。由于python 的面向对象机制和其他语言java C# 等不同,所以《剑指offer》的后面俩种方法在python中实现可能比较困难,至此结束python的第一种实现Singleton的方法。但是经过我的实际测试,python中上锁,释放锁的过程并没有很耗时,很是不解,如果有哪位朋友清楚还请不吝赐教,感谢!!测试如下:

    import time
    import threading
    
    class Singleton(object):
        __lock = threading.Lock()
        __instance = None
        def __new__(cls, *args, **kwargs):
            if not Singleton.__instance:
                Singleton.__lock.acquire()
                if not Singleton.__instance:
                    time.sleep(2) #为了模仿有大量线程运行时,多个线程可能同时进入,特此暂定2秒钟
                    Singleton.__instance = super().__new__(cls)
                Singleton.__lock.release()
            return Singleton.__instance
    
    def test():
        s = Singleton()
        print('------%s\n' % s)
    
    def main():
        start = time.time()
        t = {}
        for i in range(5):
            t[i] = threading.Thread(target=test)
            t[i].start()
        for i in range(5):
            t[i].join()
        end = time.time()
        print(end-start)
    main()
    #测试:
    输出:
    ......
    2.285130739212036
    测试 未加 if 判断的情况,耗时为:
    2.279130220413208
    

    对俩种实现方式进行耗时测试,发现:做了if not Singleton.__instance:判断的实现方式 和 没做判断的实现方式 相比耗时并没有差太多,甚至前者比后者更耗时 ,(在线程说为1000的情况下测试结果一样)我就真的懵逼了!!!所以还是推荐第一种多线程下的实现方式。

    • 优点: 实例化对象的方式singleton= Singleton() 子类直接继承也是单例
    • 缺点: 暂时没想到 __new__()方法过于强大,不建议使用 ^ _ ^ 哈

    以模块导入方式:

    其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了,上代码:

    在当前目录下新建 singleton_moudle.py:
    class Singleton(object):
        #字段...
        data = None
        # 方法
        def hh(self):
            pass
    
    singleton = Singleton()
    ......
    然后在当前文件中,将  singleton  导入即是一个单例
    在 test.py 中
    from singleton_moudle import singleton
    print(singleton)
    

    优点:实现方式简单,而且也是天然的多线程
    缺点:耦合性较强,在导入模块的时候就生成占用空间,且不能实现单例的继承

    使用 装饰器 (decorator)

    import threading
    
    def singleton(cls):
        __singleton = {}
        def mySingleton(*args, **kwargs):
            if cls not in __singleton:
                __singleton[cls] = cls(*args, **kwargs)
            return __singleton[cls]
        return mySingleton
    
    @singleton
    class A(object):
        data = None
        
        def hh(self):
            print('ggggggg')
    
    

    该方法可谓是绝妙啊。简直就是一个单例库:
    优点:可以将任意的类装饰为单例类,方便
    缺点:也是适用于单线程,当然可以通过加锁实现多线程,也是种极推荐的方法

    使用 面向对象的常用方式: @staticmethod@classmethod

    class Singleton(object):
        __instance = None
        __lock = threading.Lock()
        
        def __init__(self):
            raise SyntaxError('can not instance, please use get_instance')
            
        @classmethod
        def get_instance(cls):
            Singleton.__lock.acquire()
            if not Singleton.__instance:
                time.sleep(2)
                Singleton.__instance = object.__new__(cls)
            Singleton.__lock.release()
            return Singleton.__instance
    
    t = []
    def test():
        singleton = Singleton.get_instance()
        print(str(singleton)+'\r\n')
    print('------------------start')
    for i in range(5):
        t.append(threading.Thread(target=test))
        t[i].start()
    for i in t:
        i.join()
    print('------------------end')
    

    我们在类的__init__方法中手动抛出异常,说明不能使用常规的方式来生成实例,而是使用了,类方法,或者静态方法get_instance来获取一个实例。使用@staticmethod @classmethod 类似于java中实现单例模式。此处只实现了用@classmethod 装饰的方法,@staticmethod方法类似。可以查看参考文件 python中7种实现单例模式的方法查看具体代码

    • 优点:使用和其他面向对象语言类似的方式,容易接受
    • 缺点:实际是有风险的,s = Singleton() 会报错,但实际s确实是一个 Singleton的实例,不是严格意义上的单例

    使用 变量名覆盖的方式:即对象覆盖类,并重写__call__方法

    class Singleton(object):
        def hh(self):
            print('hhh')
            
        def __call__(self):
            return self
    
    Singleton = Singleton()
    s1 = Singleton()
    s2 = Singleton()
    print(s1)
    print(s2)
    

    先创建一个Singleton的实例对象,将该对象赋值给Singleton变量,此时Singleton不再指向类了,而是指向了一个具体的实例对象;然后当调用 Singleton() 时,因为重写了 对象的 __call__ 方法,所以直接返回对象本身;该方法也是很独特,虽然很简单,但是不推荐

    • 优点: 实现简单,天然的多线程
    • 缺点: 分层不明显,不能继承,且对象像是从石头缝里蹦出来的,很奇诡

    讲这种方法,并不希望大家这样这样实现单例,而是这种思维也是很有趣的,想突出以下,可见python的灵活性,此处有个知识点:python 中一切皆对象 一个对象是否可以被像函数一样执行,要看它是否实现了__call__只要给一个对象实现了__call__方法,那么这个对象就是可被执行的。 其实类来实例化一个对象也是调用了类的__call__方法;特别注意此处是修改了对象的__call__方法而非 类的__call__方法,所以第一步Singleton= Singleton()真实的返回了一个Singleton对象的实例,但是再次调用a=Singleton()时,此处调用了对象的__call__方法直接返回对象本身。从而实现了单例,如果不懂请看我的另一篇文章:《python 实例化对象的过程 详解》

    使用 元类 (metaclass)

    因为元类在python 比较复杂,且一般用的很少。此处我也不太想深究,有兴趣的同学可以自行google,或者查看我下面的参考资料,见谅!!

    总结:

    python 实现单例模式的方法有很多,但是推荐使用

    • __new__方式
    • 装饰器方式
    • python 中上锁与释放锁的过程貌似不耗时(待定)
    • python 中一切皆对象,若对象实现了__call__方法 则可被调用执行

    声明:

    本人也是python 小白,如果上述内容有讲的不对的地方还请各位批评指点。将不胜感激,再次感谢~~~

    参考资料:

    相关文章

      网友评论

        本文标题:python 单例模式

        本文链接:https://www.haomeiwen.com/subject/ilqciftx.html