python 单例模式

单例模式 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 小白,如果上述内容有讲的不对的地方还请各位批评指点。将不胜感激,再次感谢~~~

参考资料:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容