python实现单例模式的5种方法

单例模式是最常使用的一种设计模式,该模式的目的是确保在一个系统中,一个类只有一个实例,本文讨论python实现单例模式的5种方法。

1. 使用模块

模块天然就是单例的,因为模块只会被加载一次,加载之后,其他脚本里如果使用import 二次加载这个模块时,会从sys.modules里找到已经加载好的模块,模块里的对象天然就是单例的,即使是在多线程环境下也是如此。

编写脚本my_singleton.py

class Singleton():
    def __init__(self, name):
        self.name = name

    def do_something(self):
        pass

singleton = Singleton('模块单例')

在其他脚本里

from my_singleton import singleton

在任何引用singleton的脚本里,singleton都是同一个对象,这就确保了系统中只有一个Singleton的实例。

这种方法是官方所推荐的,它简单,代码编写容易,不需要考虑多线程的问题,当然,你也可以挑刺说在其他脚本里,还是可以主动的创建Singleton的实例对象。没错,你想搞破坏,总是能找到方法,但设计模式的目的不是为了防止人为搞破坏,而是让系统解耦,让系统易于理解和工作。

2. 使用装饰器

编写一个单例模式的装饰器,来装饰那些需要支持单例的类

def Singleton(cls):
    instance = {}

    def _singleton_wrapper(*args, **kargs):
        if cls not in instance:
            instance[cls] = cls(*args, **kargs)
        return instance[cls]

    return _singleton_wrapper


@Singleton
class SingletonTest(object):
    def __init__(self, name):
        self.name = name


slt_1 = SingletonTest('第1次创建')
print(slt_1.name)
slt_2 = SingletonTest('第2次创建')
print(slt_1.name, slt_2.name)

print(slt_1 is slt_2)

程序输出结果

第1次创建
第1次创建 第1次创建
True

创建slt_2 对象时,instance 字典中已经存在SingletonTest 这个key,因此直接返回了第一次创建的对象,slt_1 和 slt_2 是同一个对象。

在多线程环境下,这种设计方法是不安全的,多个线程同时判断cls是否在instance字典中,得到的返回结果都是False,于是这些线程都会去创建对象,为了避免这种情况,加上一把RLock锁

from threading import RLock
single_lock = RLock()

def Singleton(cls):
    instance = {}

    def _singleton_wrapper(*args, **kargs):
        with single_lock:
            if cls not in instance:
                instance[cls] = cls(*args, **kargs)
        return instance[cls]

    return _singleton_wrapper

3. 使用类

class Singleton(object):

    def __init__(self, name):
        self.name = name

    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance

single_1 = Singleton.instance('第1次创建')
single_2 = Singleton.instance('第2次创建')

print(single_1 is single_2)     # True

instance方法会先检查是否存在类属性_instance, 如果不存在,则创建对象,并返回。这个设计虽然实现了单例模式,但在多线程环境下不安全,多个线程同时检查Singleton类是否拥有_instance属性,得到的结果是否False, 则这些线程都会执行对象的创建工作,最后创建出来的对象才是最终的对象,为了在多线程环境下保证数据安全,在需要并发枷锁的地方加上RLock锁

from threading import RLock
class Singleton(object):
    single_lock = RLock()

    def __init__(self, name):
        self.name = name

    @classmethod
    def instance(cls, *args, **kwargs):
        with Singleton.single_lock:
            if not hasattr(Singleton, "_instance"):
                Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance

4. 基于__new__方法实现

__new__方法是构造函数,是真正的用来创建对象的,那么在创建对象的时候进行控制,不是更方便么

from threading import RLock

class Singleton(object):
    single_lock = RLock()

    def __init__(self, name):
        self.name = name

    def __new__(cls, *args, **kwargs):
        with Singleton.single_lock:
            if not hasattr(Singleton, "_instance"):
                Singleton._instance = object.__new__(cls)

        return Singleton._instance

single_1 = Singleton('第1次创建')
single_2 = Singleton('第2次创建')

print(single_1.name, single_2.name)   # 第2次创建 第2次创建
print(single_1 is single_2)     # True

这种方法同样实现了多线程环境下的安全的单例模式,但有一个小小的问题,第二次创建对象时,虽然返回了单例,但是修改了单例的name属性,前面已经介绍的三个方法,一旦单例对象被创建,此后的创建都返回第一次创建的对象且不会修改对象的属性。

之所有这样的差别,是因为前面三个方法,都只调用__new__方法一次,__new__方法创建对象后,会调用一次__init__来初始化对象,而方法4会多次调用__new__方法,虽然每次返回的都是同一个对象,但是会立即调用__init__方法,这样就修改了name属性,如果不希望name属性在多次创建对象过程中被修改,只需要做一个小小的修改即可

    def __init__(self, name):
        if hasattr(self, 'name'):
            return
        self.name = name

一旦发现name属性已经被初始化,就不在执行初始化的代码。

5. 使用元类

from threading import RLock

class SingletonType(type):
    single_lock = RLock()

    def __call__(cls, *args, **kwargs):   # 创建cls的对象时候调用
        with SingletonType.single_lock:
            if not hasattr(cls, "_instance"):
                cls._instance = super(SingletonType, cls).__call__(*args, **kwargs)     # 创建cls的对象

        return cls._instance


class Singleton(metaclass=SingletonType):
    def __init__(self, name):
        self.name = name


single_1 = Singleton('第1次创建')
single_2 = Singleton('第2次创建')

print(single_1.name, single_2.name)     # 第1次创建 第1次创建
print(single_1 is single_2)     # True

在5种python实现单例模式的方法中,基于元类的实现,可能是最难理解的一个。

class Singleton(metaclass=SingletonType) 这行代码定义了一个类,这个类是元类SingletonType 的实例,是元类SingletonType的__new__构造出来的,Singleton是实例,那么Singleton('第1次创建')就是在调用元类SingletonType 的__call__方法,__call__方法可以让类的实例像函数一样去调用。

在__call__方法里,cls就是类Singleton,为了创建对象,使用super来调用__call__方法,而不能直接写成cls(*args, **kwargs), 这样等于又把SingletonType的__call__方法调用了一次,形成了死循环。

总结

5种方法,都可以实现多线程环境下的安全的单例模式,方法1最简单,无法避免人为的创建多个实例。其余的方法,可以避免人为的创建出多个实例,但需要在并发操作上加锁。

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案