0x00:单例模式

  • 单例模式

单例模式(Singleton Pattern),又称单子模式,是一种常见的软件设计模式,属于创建型模式的一种。在这个设计模式中,单例对象的类(class)必须保证有切仅能一个实例(instance)存在。

通过单例模式可以确保系统中一个类只有一个实例而且该实例可以被外界访问,从而能控制系统中存在的实例个数并节约系统资源。

  • 单例模式的特点

    1. 单例类只能有一个实例
    2. 单例类必须自己创建自己的唯一实例
    3. 单例类必须给所有其他对象提供这一实例(即该实例可以被外界访问)
  • 单例模式的优点

    • 由于单例模式下,内存中有且仅有一个实例,减少了内存开销;解决了需要频繁创建和销毁内存实例对象的性能问题。
    • 由于单例模式下,系统只生成一个实例,所以减少了系统的性能开销。当一个对象的产生时需要较多的系统资源时,例如:读取系统配置文件等,采用单例模式是一个不错的选择。
    • 单例模式可以避免对资源的多重占用,例如在对文件进行写操作时,由于单例模式下内存中仅有一个实例存在,有效地避免了对同一个文件的同时写操作,减少脏数据的产生。
    • 单例模式下可以在系统设置全局的访问点,优化和共享资源访问,例如构建一个单例类,负责读取系统配置文件,为系统提供配置信息。
  • 单例模式的缺点

    • 单例模式一般没有接口,不能继承,拓展性很低。
    • 单例模式是不利于测试;在并行开发环境中,如果单例模式没有完成,则对于需要依赖其进行的测试是无法正常进行的,因为它没有接口也不能使用mock的方式模拟一个对象。
    • 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。

0x01:如何优雅的使用Python实现单例模式

一、使用模块

Python 的模块就是天然的单例模式

因为模块在第一次导入时,会生成 .pyc 文件;当第二次导入该模块时,解释器就会直接加载 .pyc 文件,而不会再次执行模块代码。

因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。

"""
MySingleton.py
"""
class Singleton(object):
    def foo(self):
        pass
singleton = Singleton()

新建一个新的py文件,将上面文件所写的对象通过模块的方式导入

"""
UseTheSingleton.py
"""
from MySingleton import singleton
print('第一次导入,ID:',id(singleton))

from MySingleton import singleton
print('第二次导入,ID:',id(singleton))

from MySingleton import singleton
print('第三次导入,ID:',id(singleton))

执行结果

执行结果

第一次导入,ID: 2044846947152
第二次导入,ID: 2044846947152
第三次导入,ID: 2044846947152

二、使用装饰器

使用装饰器装饰函数,实现单例模式是一个不错的方法;而且这个方式无论单线程还是多线程不会出现蜜汁错误。

"""
decorator.py
"""

def Singleton(cls):
    _instance = {}

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


@Singleton
class Foo(object):
    pass


foo1 = Foo()
foo2 = Foo()
print('foo1,id:',id(foo1))
print('foo2,id:',id(foo2))

执行结果

执行结果

三、使用类

在Python中,使用类的方式也可以实现单例模式,但是用类的方式实现单例,如果代码编写不严谨,就会闹出奇怪的BUG,比如这下面的代码

"""
SingletonClassBUG.py
"""
class Singleton(object):

    def __init__(self):
        pass

    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance
    
"""
单线程下执行
"""
def task(arg):
    obj = Singleton.instance()
    print(id(obj))

for i in range(6):
    task(i)

上面的代码看似没有什么问题,在单线程执行的情况下也能正常实现单例模式。

单线程下执行情况

但是!若是在多线程的环境下执行的话,似乎就无法正常实现了。如下所示:

"""
SingletonClassBUG.py
"""
class Singleton(object):

    def __init__(self):
        pass

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

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

执行结果

多线程执行结果

嗯嗯嗯?似乎没什么问题?!? 难道其实这样就可以正常实现单例模式了?

当然不是啦 ~~ 现在的类中似乎没有执行一些耗时操作,若是在初始化中进行一些耗时操作(例如:读取较大文件、进行网络访问、查询数据库等),效果就出来了~

接下来,我们在类初始化__init__时用time.sleep模拟耗时操作

"""
SingletonClassBUG.py
"""
class Singleton(object):

    def __init__(self):
        time.sleep(1)

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

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

执行结果

芜湖

芜湖~ 起飞~ 加了耗时操作后,这很明显没有实现我们想要的单例模式。

那么,对付这种情况,我们该如何解决呢? 答案就是 加锁

"""
SingletonClass.py
"""
class Singleton(object):
    _instance_lock = threading.Lock()

    def __init__(self):
        time.sleep(1)

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


def task(arg):
    obj = Singleton.instance()
    print(id(obj))

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

执行结果

加锁后

但是这样实现的单例模式必须通过Singleton.instance()来实例化对象,若是直接Singleton()得到的就不是单例模式了。

四、基于__new__魔法方法实现

通过上面所写的方式,实现单例时,为了保证其线程安全,需要在内部加锁

不过,我们知道,Python在实例化一个对象时,第一时间先执行了类中的__new__方法,实例化对象;

重写__new__

其次才执行类中的__init__方法,对这个对象进行初始化;

故,我们可以通过重写__new__方法的方式来实现单例模式。

class Singleton(object):

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance

    def __init__(self):
        time.sleep(1)


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


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

执行结果

通过__new__方式实现单例

采用这种方式实现单例模式,不仅能在不加锁的情况下保证其线程安全,而且还能和平时实例化对象一样采用obj = Singleton()实例化对象。

五、基于metaclass方法实现

  1. 类由type创建,创建类时,type的__init__方法自动执行,类() 执行type的 __call__方法(类的__new__方法,类的__init__方法)
  2. 对象由类创建,创建对象时,类的__init__方法自动执行,对象()执行类的 call 方法
class Foo:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        pass

obj = Foo()
# 执行type的 __call__ 方法,调用 Foo类(是type的对象)的 __new__方法,用于创建对象,然后调用 Foo类(是type的对象)的 __init__方法,用于对对象初始化。

obj()    # 执行Foo的 __call__ 方法    

元类的使用

class SingletonType(type):
    def __init__(self,*args,**kwargs):
        super(SingletonType,self).__init__(*args,**kwargs)

    def __call__(cls, *args, **kwargs): # 这里的cls,即Foo类
        print('cls',cls)
        obj = cls.__new__(cls,*args, **kwargs)
        cls.__init__(obj,*args, **kwargs) # Foo.__init__(obj)
        return obj

class Foo(metaclass=SingletonType): # 指定创建Foo的type为SingletonType
    def __init__(self,name):
        self.name = name
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

obj = Foo('xx')

使用元类实现单例模式

class Singleton(type):
    _instance_lock = threading.Lock()

    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            with Singleton._instance_lock:
                if not hasattr(cls, "_instance"):
                    cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instance

class Foo(metaclass=Singleton):
    def __init__(self):
        time.sleep(1)

def task(arg):
    obj = Foo()
    print(id(obj))

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

执行结果

元类实现单例模式


0x02:参考文献

Last modification:November 3rd, 2020 at 05:19 pm
给狐宝打点钱⑧