简单聊聊Python设计模式:单例模式

243 阅读5分钟

本文已参与掘金创作者训练营第三期,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力

📖前言

书不是看的是用的

在这里想强调一下学习方法,总有很多小伙伴对学习知识有疑惑,明明看了、看的时候也懂了,但到了实际使用的时候却用不上。或者有时候在想是不要是有更加生动的漫画或者什么对比会好些,当然这些方式可能会加快一个新人对知识的理解速度。但只要你把学习视频当电影看、学习书籍当故事看,就很难掌握这项技术栈。只有你把它用起来,逐字逐句的深挖,一点点的探求,把各项遇到的盲点全部扫清,才能让你真的掌握这项技能。

本篇主要介绍一下关于 Python 的单例模式,即让一个类对象有且只有一个实例化对象。


🚀单例模式

Java 中的单例:

  • 单例模式可以说是整个设计中最简单的模式之一,而且这种方式即使在没有看设计模式相关资料也会常用在编码开发中。
  • 因为在编程开发中经常会遇到这样一种场景,那就是需要保证一个类只有一个实例哪怕多线程同时访问,并需要提供一个全局访问此实例的点。
  • 综上以及我们平常的开发中,可以总结一条经验,单例模式主要解决的是,一个全局使用的类频繁的创建和消费,从而提升提升整体的代码的性能。

Python 中的简单使用

内容:保证一个类只有一个实例,并提供一个访问它的全局访问点。
适用场景:当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时
优点:
    对唯一实例的受控访问
    单例相当于全局变量,但防止了命名空间被污染
与单例模式功能相似的概念:全局变量、静态变量(方法

🐱‍🏍一、使用 __new__ 方法(基类)

  要实现单例模式,即为了让一个类只能实例化一个实例,那么我们可以去想:既然限制创建实例,那么我们可以修改其创建实例的根源即可,那就是父类 __new__ 方法。

  注意:不能使用自身的 __new__() 方法,应为自身这个类去进行实例化,是调用父类的 __new__ 方法,若调用自身的 __new__ 方法,那不就死循环了么,可以参考我的另外一篇博客:python 类的 __new__()

class SingleTon(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super().__new__(cls, *args, **kwargs)
        # return super().__new__(cls, *args, **kwargs)
        return cls._instance

class Foo(SingleTon):
    pass

if __name__ == '__main__':
    f1 = Foo()
    f2 = Foo()
    print('f1的id:',id(f1))
    print('f2的id:',id(f2))

# f1的id: 2151027475064
# f2的id: 2151027475064

注释:通过修改父类的 __new__ 方法,去干涉子类的实例化创建实例,实现只能一个类只能有一个实例的效果。

😎二、使用闭包的形式

  无论怎么实现,均为了让一个类只有一个实例对象,那么我们可以当类创建一个实例,我们就把使用一个变量把该实例记录下来,若该变量已经有实例了,便可把该变量返回即可,不让其进行创建新的实例。

def singleton(cls, *args, **kwargs):
    instances = None
    def get_instance(*args, **kwargs):
        nonlocal instances
        if instances is None:
            instances = cls(*args, **kwargs)
        return instances
    return get_instance

@singleton
class Bar(object):
    def __init__(self,name):
        self.name = name

if __name__ == '__main__':
    b1 = Bar('alex')
    b2 = Bar('hello')
    print(b1.name)
    print(b2.name)

注:为了实现单例模式,可以在类创建实例的时候对其进行限制,即通过判断该类是否已经创建了实例,若没有进行过实例化,则我们让其进行实例化,反之,返回原有对象即可。

✨三、使用元类的方式

首先我们需要明确的是,当我们使用元类时:

  1、类由 type 创建,创建类时,type__init__ 方法自动执行,类() --》执行type的 __call__ 方法(类的 __new__ 方法,类的 __init__ 方法)。

  2、对象由类创建,创建对象时,类的 __init__ 方法自动执行,对象() -- 》执行类的 __call__ 方法。

  当我们调用 类() 时就是一个实例化的过程吗,就自动回执行 type 中的 call 方法,所以我们需要去实现单例模式,只需要在call方法中对其进行限制是不是就可以达到我们想要的效果呢?试一试

class SingleTonType(type):

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

    def __call__(cls, *args, **kwargs):
        _instance = None  # 设置一个变量,用来存储是否创建实例
        print('cls:',cls)
        if _instance is None:
            obj = cls.__new__(cls,*args, **kwargs)  # 会一直找到能创建实例的父类,创建实例
            cls.__init__(obj, *args, **kwargs) # 构造方法去丰富该实例
            cls._instance = obj   # 并将变量修改的创建的实例
        return _instance

class Foo(metaclass=SingleTonType):
    def __init__(self,name):
        self.name = name

if __name__ == '__main__':
    obj1 = Foo('hello')  # 会调用type类(SingleTonType)中的call方法
    obj2 = Foo('world')

执行结果:

cls: <class '__main__.Foo'>
cls: <class '__main__.Foo'>
1841145040 1841145040

达到了一个类只拥有一个实例对象的效果


🎉总结

  • 实现单例模式,只需要在类进行实例化的时候,对其进行限制即可。比如在类进行实例化时,会调用父类的 __new__ 方法,若使用元类,会调用 call() 方法,那么我们只需要在这两个方法中对其加以限制即可。
  • 使用修饰器闭包的利用的也是同样的原理。
  • 更多参考精彩博文请看这里:《陈永佳的博客》
  • 喜欢博主的小伙伴可以加个关注、点个赞哦,持续更新嘿嘿!