python-单例模式趣话

一天,小猿(程序猿)正在安静的写代码(制造bug),突然狂风大作,只见,小猿的电脑屏幕出现一个无底的黑洞,小猿被卷入电脑,跌入洞中……

不知过了多久,小猿醒了过来,缓缓的睁开眼,发现自己躺在一个陌生的地方,而此时他眼前走过的是一个个机器人,这些机器人背后都有代号,有文件、内存、磁盘等等。小猿还没来得及惊慌,就被一群背后写着“进程”的机器人组织开会,只见台上坐着一个大佬,背后写着赫赫的三个大字母:CPU,小猿心想,这是进了计算机内部窝了啊,望着身边一个个面无表情的机器人,瞬间感觉弱小、可怜、无助、又饥饿……

原来,这位CPU大佬想做一个应用, 在整个应用的运行过程中,一直保持并维护着唯一的实例。所以组织大家集结思路,谁能做到,承诺一顿大餐作为奖励。小心想,这不就是单例吗,就这?能难倒我?于是小本着助人为乐(能吃饱饭)的原则,踊跃发言并给出了实现方案:

1.使用装饰器方式实现

1.1函数装饰器方式

def singleton(cls):
    # 创建一个字典用来保存被装饰类的实例对象
    _instance = {}

    def _singleton(*args, **kwargs):
        # 判断这个类有没有创建过对象,没有新创建一个,有则返回之前创建的
        if cls not in _instance:
            _instance[cls] = cls(*args, **kwargs)
        return _instance[cls]
    return _singleton

@singleton
class A(object):
    def __init__(self, a=0):
        self.a = a


a1 = A(1)
a2 = A(2)
# id()函数可以获取对象的内存地址,同一内存地址即为同一对象
print(id(a1), id(a2))

1.2类装饰器方式

class Singleton(object):
    def __init__(self, cls):
        self._cls = cls
        self._instance = {}

    def __call__(self):
        if self._cls not in self._instance:
            self._instance[self._cls] = self._cls()
        return self._instance[self._cls]


@Singleton
class B(object):
    def __init__(self):
        pass


b1 = B()
b2 = B()
print(id(b1), id(b2))

CPU大佬看了摇了摇头,若有所思,说,这样是可以实现,但是装饰器的原理和执行顺序很复杂,我不能接受。

小猿反驳说:装饰器是基于面向切面编程思想来实现的,具有很高的解耦性和灵活性,而且本质其实不难理解,装饰器本质上其实还是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。装饰器的本质是函数,主要用来装饰其他函数,也就是为其他函数增加功能……

”打住打住“,没等小说完,CPU就打断了他,“我不想听你这些繁琐的解释,还有其他实现方式吗,没有就退下吧!”

心想:这真是一个难缠的人啊!为了面子(主要还是想吃饭),忍了,给出方案2

2.使用类的方式实现

class Singleton(object):
    def __init__(self, *args, **kwargs):
        pass

    @classmethod
    def get_instance(cls, *args, **kwargs):
        # hasattr() 函数用于判断对象是否包含对应的属性,这里是看看这个类有没有_instance属性
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton(*args, **kwargs)

        return Singleton._instance


s1 = Singleton()  # 使用这种方式创建实例的时候,并不能保证单例
s2 = Singleton.get_instance()  # 只有使用这种方式创建的时候才可以实现单例
s3 = Singleton()
s4 = Singleton.get_instance()

print(id(s1), id(s2), id(s3), id(s4))

小猿解释道: 这种方式的思路就是,调用类的get_instance方法去创建对象,get_instance方法会判断之前有没有创建过对象,有的话也是会返回之前已经创建的对象,不再新创建,但是这样有一个弊端,就是在使用类创建(s3 = Singleton()这种方式)的时候,就不能保证单例了,也就是说在创建类的时候一定要用类里面规定的get_instance方法创建。

CPU说到:“那你解决一下喽”。

小猿又开始在心里嘀咕:站着说话不腰疼,我要不是看上那顿大餐,我才不理你呢!给你方案3

3.使用__new__函数实现

class Singleton(object):

    def __init__(self):
        print("__init__")

    def __new__(cls, *args, **kwargs):
        print("__new__")
        if not hasattr(Singleton, "_instance"):
            print("创建新实例")
            Singleton._instance = object.__new__(cls)
        return Singleton._instance


obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2)

当python实例化一个对象时,是先执行类的__new__方法,当我们没写__new__方法时,默认调用基类object的__new__方法然后再执行类的__init__方法,对这个对象进行初始化,所有我们可以基于这个,去实现单例模式 ,我们通过hasattr(Singleton, "_instance")(其中hasattr()的功能是判断一个对象有没有指定的属性)去判断之前有没有实例化过对象,如果有,就直接返回,没有就新创建一个。

附上控制台输出:可以看出,同样实现了单例。

这时CPU大佬说到:”看输出其实执行了两遍__init__方法,既然是同一个对象,初始化两次,这不合理。“

”没关系,那优化一下!“小猿说。

class Singleton(object):

    def __init__(self):
        if not hasattr(Singleton, "_first_init"):
            print("__init__")
            Singleton._first_init = True

    def __new__(cls, *args, **kwargs):
        print("__new__")
        if not hasattr(Singleton, "_instance"):
            print("创建新实例")
            Singleton._instance = object.__new__(cls)
        return Singleton._instance

通过控制台输出我们可以看到,__init__方法只执行了一次。

到这时,大佬似乎有些满意。小猿又说道: ”为了保证线程安全,我们还可以在类内部加入锁机制!

import threading


class Singleton(object):
    _instance_lock = threading.Lock() # 线程锁

    def __init__(self):
        if not hasattr(Singleton, "_first_init"):
            print("__init__")
            Singleton._first_init = True

    def __new__(cls, *args, **kwargs):
        print("__new__")
        with Singleton._instance_lock:
            if not hasattr(Singleton, "_instance"):
                print("创建新实例")
                Singleton._instance = object.__new__(cls)
        return Singleton._instance


def task(arg):
    obj = Singleton()
    print(obj)


for i in range(10):
    t = threading.Thread(target=task, args=[i, ])
    t.start()

此时,CPU大佬露出了欣慰的笑容,大赞,众机器人也都欢呼着,CPU大佬也兑现了大餐的承诺,让人带着小猿去吃大餐。

小猿看到满桌子的山珍海味,开心的合不拢嘴,大口大口地吃了起来……

叮铃铃,叮铃铃,闹铃响了……原来这一切只是小猿做的梦,而此时小猿的嘴里还咬着一只袜子……哈哈哈

借此,梳理开发中经常用到的单例模式,其实python实现单例的方式还有很多,像通过模块、metaclass方式等等,这里不一一实现,感兴趣的朋友可以自行百度。

最后,感谢女朋友在生活中,工作上的包容、理解与支持 !

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章