• 【Python学习笔记】在Python中如何实现单例模式


    在 Python 中如何实现单例模式

    单例模式Singleton Pattern):是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。

    方法一

    实例化一个的对象,要用时直接 import 导如这个对象,而不是再实例化一个,这样就做到了单例模式了,每次都用的是同一个对象。

    方法二

    我们知道,当我们实例化一个对象时,是先执行了类的 __new__ 方法(我们没写时,默认调用 object.new),实例化对象;然后再执行类的 __init__ 方法,对这个对象进行初始化,所有我们可以基于这个,实现单例模式。

    class Count(object):
        def __init__(self):
            print('将对象初始化')
    
        def __new__(cls, *args, **kwargs):
            print('new一个对象')
            cls.obj = object.__new__(cls)
            return cls.obj
    
    Count()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    从以上结果中我们看到,使用类创建对象时,先自动执行了 new 方法创建对象,然后返回给 init 进行初始化操作。

    接下来进行改写。

    class Count(object):
        # 设置一个类属性来判断这个类是否实例化过对象
        instance = None
    
        def __init__(self):
            pass
    
        def __new__(cls, *args, **kwargs):
            if cls.instance == None:
                cls.instance = object.__new__(cls)
                return cls.instance
            else:
                return cls.instance
    
    t1 = Count()
    t2 = Count()
    t3 = Count()
    print(t1)
    print(t2)
    print(t3)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述
    以上,通过 new 方法内判断是否创建过对象,来实现单例模式,我们实例化三个对象 t1t2t3 三个对象内存地址都是一样的,这就说明他们是同一个对象。

    方法三

    通过装饰器实现单例模式,任意一个类使用了该装饰器就会变成一个单例模式的类。

    装饰器是 Python 中非常有特色的语法,用一个函数去装饰另一个函数或类,为其添加额外的能力。通常通过装饰来实现的功能都属横切关注功能,也就是跟正常的业务逻辑没有必然联系,可以动态添加或移除的功能。装饰器可以为代码提供 缓存、代理、上下文环境 等服务,它是对设计模式中代理模式的践行。

    装饰器的作用和功能:

    • 引入日志
    • 函数执行时间统计
    • 执行函数前预备处理
    • 执行函数后的清理功能
    • 权限校验等场景
    • 缓存

    总的来说,装饰器 wrapper 是一个函数的工厂,他接收一个函数参数,返回的也是一个函数。一般其作用是将原有函数添加上新的一些功能,然后将新的功能更强的函数返回。

    def Single_Class(cls):
        instance = {}
    
        def single(*args, **kwargs):
            if cls not in instance:
                # 如果类没有创建过,那就new一个新的,并保存在字典里
                instance[cls] = cls(*args, **kwargs)
                return instance[cls]
            else:
                return instance[cls]
        return single
    
    @Single_Class
    class A():
        pass
    
    t1 = A()
    t2 = A()
    print(t1)
    print(t2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    以上,我们写了一个装饰器,该装饰器将所有的对象都存储到字典里,如果之前创建过的对象就直接返回,如果没有创建过的对象那就 new 一个,同一个类我们实例化了 t1t2,但是他们的内存地址都是同一个,说明我们已经实现了单例模式的类装饰器。

    接下来对上述代码做一些小的改动。

    from functools import wraps
    
    def Single_Class(cls):
        instance = {}
    
        @wraps(cls) # wraps是在装饰器中使用,保留原来函数的属性
        def single(*args, **kwargs):
            if cls not in instance:
                # 如果类没有创建过,那就new一个新的,并保存在字典里
                instance[cls] = cls(*args, **kwargs)
                return instance[cls]
            else:
                return instance[cls]
        return single
    
    @Single_Class
    class A():
        pass
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在写装饰器的时候,带装饰功能的函数(上面代码中的 single 函数)通常都会用functools模块中的 wraps 再加以装饰,这个装饰器最重要的作用是给被装饰的类或函数动态添加一个 __wrapped__ 属性,这个属性会将被装饰之前的类或函数保留下来,这样在我们不需要装饰功能的时候,可以通过它来取消装饰器,例如可以使用 A = A.__wrapped__ 来取消对 A 类做的单例处理。

    需要提醒大家的是:上面的单例并不是线程安全的,如果要做到线程安全,需要对创建对象的代码进行加锁的处理。在 Python 中可以使用 threading 模块的 RLock 对象来提供锁,可以使用锁对象的 acquirerelease 方法来实现加锁和解锁的操作。当然,更为简便的做法是使用锁对象的 with 上下文语法来进行隐式的加锁和解锁操作。

    方法四

    使用元类实现单例模式。这个方法和方法二其实思想上是一致的。

    class SingletonMeta(type):
        """自定义单例元类"""
    
        def __init__(cls, *args, **kwargs):
            cls.__instance = None
            super().__init__(*args, **kwargs)  # 继承父类中的__init__
    
        def __call__(cls, *args, **kwargs):
            if cls.__instance is None:
                cls.__instance = super().__call__(*args, **kwargs)
            return cls.__instance
    
    class President(metaclass=SingletonMeta):
        pass
    
    t1 = President()
    t2 = President()
    print(t1)
    print(t2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    Python 是面向对象的编程语言,在面向对象的世界中,一切皆为对象。对象是通过类来创建的,而类本身也是对象,类这样的对象是通过元类来创建的。我们在定义类时,如果没有给一个类指定父类,那么默认的父类是 object;如果没有给一个类指定元类,那么默认的元类是 type。通过自定义的元类,我们可以改变一个类默认的行为,就如同上面的代码中,我们通过元类的 __call__ 魔术方法,改变了 President 类的构造器那样。

    单例模式的优点:

    • 节约内存,因为不管实例化多少个 对象都实际指向一个内存地址;
    • 多个地方创建的实例他们的属性互动,比如:我们只给实例化对象 t1 添加了一个 name 属性,而 t2 和 t3 都拥有了这个属性,这就给我们节约了很多的时间。

    关于单例模式,还有很多其他的应用场景。通常一个对象的状态是被其他对象共享的,就可以将其设计为单例。例如:

    • 项目中使用的数据库连接池对象和配置对象通常都是单例,这样才能保证所有地方获取到的数据库连接和配置信息是完全一致的。
    • 而且由于对象只有唯一的实例,因此从根本上避免了重复创建对象造成的时间和空间上的开销,也避免了对资源的多重占用。
    • 再举个例子,项目中的日志操作通常也会使用单例模式,这是因为共享的日志文件一直处于打开状态,只能有一个实例去操作它,否则在写入日志的时候会产生混乱。

    参考资料

    【1】【建议收藏】50 道硬核的 Python 面试题!
    【2】python 实现单例模式的几种方法实例详解

  • 相关阅读:
    linux上手动安装luasql的包(centos7)
    【Spring Boot】Day03
    二叉查找树的插入
    linux 创建git项目并提交到gitee(保姆式教程)
    链的解构主义:一览 9 大模块化公链
    Ubuntu20.04安装ROS-noetic
    ubuntu下yolov5 tensorrt模型部署
    全志T507 UART复用方法-飞凌嵌入式知识库
    如何正确求人办事?
    图像处理用什么神经网络,神经网络如何识别图像
  • 原文地址:https://blog.csdn.net/be_racle/article/details/126612162