• 【设计模式】【单例模式】python实现单例模式的几种方式


    一、什么是单例

    单例模式可以保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    二、为什么要使用单例?

    适用于当类只能有一个实例而且客户可以从一个众所周知的访问点访问它,例如访问数据库、MQ等。
    对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。

    三、Python中怎么实现单例?

    3.1-利用python的模块特性(线程安全)

    3.1.1 实例

    python的模块在第一次被导入时会执行一遍这个模块的代码,之后就不再执行。针对这个特性,可以实现单例。

    # test.py
    class SingleTon(object):
        def __init__(self, name):
            self.name = name
        def run(self):
            print(self.name)
    s = SingleTon(name='vth')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    # main.py
    from test import s as s1
    from test import s as s2
    
    def show():
        print(s1, id(s1))
        print(s2, id(s2))
    
    show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    输出:

    <test.SingleTon object at 0x10b20dee0> 4481670880
    <test.SingleTon object at 0x10b20dee0> 4481670880
    
    • 1
    • 2

    可以证明只拿到了一个对象。

    3.1.2 线程安全测试代码

    import threading
    import time
    
    def show():
        time.sleep(1)
        from test import s
        print(s, id(s))
    
    
    def main():
        l = []
        for i in range(3):
            l.append(threading.Thread(target=show))
        for i in l:
            i.start()
    
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.2 利用python的new方法(非线程安全, 需加锁)

    new是创造实例的方法,init是初始化实例的方法。new是类的方法,init是实例的方法。

    3.2.1实例(非线程安全):

    class SingleTon:
        _instance = None
        def __new__(cls, *args, **kwargs):
            if cls._instance is None:
                cls._instance = super().__new__(cls)
            return cls._instance
    
    def get_instance():
        s = SingleTon()
        print(s, id(s))
    
    
    if __name__ == '__main__':
        get_instance()
        get_instance()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    输出:

    <__main__.SingleTon object at 0x10d7d2ee0> 4521275104
    <__main__.SingleTon object at 0x10d7d2ee0> 4521275104
    
    • 1
    • 2

    这在单线程中工作正常。但分析一下代码,如果是多线程情况下,判断完cls._isinstance是None的时候,如果切换到别的线程,那就会创造多个实例。

    3.2.2 线程安全测试代码

    import threading
    import time
    class SingleTon:
        _instance = None
        def __new__(cls, *args, **kwargs):
            if cls._instance is None:
                time.sleep(1)
                cls._instance = super().__new__(cls)
            return cls._instance
    
    def get_instance():
        s = SingleTon()
        print(s, id(s))
    
    def main():
        l = []
        for i in range(3):
            l.append(threading.Thread(target=get_instance))
        for i in l:
            i.start()
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    输出:

    <__main__.SingleTon object at 0x104775ca0><__main__.SingleTon object at 0x104892040> 4371062848
     <__main__.SingleTon object at 0x1048f73d0>4369898656
     4371477456
    
    • 1
    • 2
    • 3

    这明显线程不安全。可以加个锁。继续测试。

    3.2.3 线程安全的代码

    import threading
    import time
    class SingleTon:
        _instance = None
        _lock = threading.Lock()
        def __new__(cls, *args, **kwargs):
            with cls._lock:
                if cls._instance is None:
                    time.sleep(1)
                    cls._instance = super().__new__(cls)
            return cls._instance
    
    def get_instance():
        s = SingleTon()
        print(s, id(s))
    
    def main():
        l = []
        for i in range(3):
            l.append(threading.Thread(target=get_instance))
        for i in l:
            i.start()
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    输出:

    <__main__.SingleTon object at 0x10caf2ca0><__main__.SingleTon object at 0x10caf2ca0><__main__.SingleTon object at 0x10caf2ca0> 4507774112
     4507774112
     4507774112
    
    • 1
    • 2
    • 3

    可能会有疑问,为什么锁加在了if语句的外面。
    这是因为,如果锁加在了if里面,那么3个线程都有可能进入if块。进入if块就必然要执行创建新实例,加跟没加一样。

    3.2.4 init执行次数测试代码

    # 单线程测试代码
    class SingleTon:
        _instance = None
        def __new__(cls, *args, **kwargs):
            if cls._instance is None:
                cls._instance = super().__new__(cls)
            return cls._instance
    
        def __init__(self, name):
            self.name = name
    
    def get_instance(args):
        s = SingleTon('vth'+str(args))
        print(s, id(s))
        print(s.name)
    
    
    if __name__ == '__main__':
        get_instance('da')
        get_instance('db')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    输出:

    <__main__.SingleTon object at 0x10fd29ee0> 4560428768
    vthda
    <__main__.SingleTon object at 0x10fd29ee0> 4560428768
    vthdb
    
    • 1
    • 2
    • 3
    • 4
    # 多线程测试代码
    import threading
    import time
    class SingleTon:
        _instance = None
        _lock = threading.Lock()
        def __new__(cls, *args, **kwargs):
            with cls._lock:
                if cls._instance is None:
                    time.sleep(1)
                    cls._instance = super().__new__(cls)
            return cls._instance
    
        def __init__(self, name):
            self.name = name
    
    def get_instance(args):
        s = SingleTon(str(args))
        print(s, id(s))
        print(s.name)
    
    def main():
        l = []
        for i in range(3):
            l.append(threading.Thread(target=get_instance, args=(i,)))
        for i in l:
            i.start()
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    输出:

    <__main__.SingleTon object at 0x10b633ca0> 4486020256
    0
    <__main__.SingleTon object at 0x10b633ca0> 4486020256
    <__main__.SingleTon object at 0x10b633ca0> 4486020256
    2
    2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可见,虽然我们用锁的方式实现了线程安全的实例,但这个实例可被多次初始化(即多次调用了init方法)。那么,我们怎么限制只能调用一次init方法呢?

    3.2.5 限制只使用一次init方法

    # 单线程
    class SingleTon:
        _instance = None
        _flag = False
        def __new__(cls, *args, **kwargs):
            if cls._instance is None:
                cls._instance = super().__new__(cls)
            return cls._instance
    
        def __init__(self, name):
            if not SingleTon._flag:
                SingleTon._flag = True
                self.name = name
    
    def get_instance(args):
        s = SingleTon('vth'+str(args))
        print(s, id(s))
        print(s.name)
    
    
    if __name__ == '__main__':
        get_instance('da')
        get_instance('db')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    输出:

    <__main__.SingleTon object at 0x103cc1970> 4358674800
    vthda
    <__main__.SingleTon object at 0x103cc1970> 4358674800
    vthda
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    看,在单线程中工作良好。但在多线程中可不行。

    # 多线程
    import threading
    import time
    class SingleTon:
        _instance = None
        _lock = threading.Lock()
        _flag = False
        def __new__(cls, *args, **kwargs):
            with cls._lock:
                if cls._instance is None:
                    time.sleep(1)
                    cls._instance = super().__new__(cls)
            return cls._instance
    
        def __init__(self, name):
            if not SingleTon._flag:
                time.sleep(1)
                SingleTon._flag = True
                self.name = name
    
    def get_instance(args):
        s = SingleTon(str(args))
        print(s, id(s))
        print(s.name)
    
    def main():
        l = []
        for i in range(3):
            l.append(threading.Thread(target=get_instance, args=(i,)))
        for i in l:
            i.start()
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    输出:

    <__main__.SingleTon object at 0x10fd41ca0> 4560526496
    0
    <__main__.SingleTon object at 0x10fd41ca0> 4560526496
    1
    <__main__.SingleTon object at 0x10fd41ca0> 4560526496
    2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    分析一下原因,为啥在多线程中不行?
    原因还是一样的。它是非线程安全的。如果有N个线程都进入到if not SingleTon._flag块中,那么它其中的代码都会执行,就实例化了多次。
    所以依然要加锁。

    3.2.6 线程安全的只使用一次init的单例方法

    import threading
    import time
    class SingleTon:
        _instance = None
        _lock = threading.Lock()
        _flag = False
        def __new__(cls, *args, **kwargs):
            with cls._lock:
                if cls._instance is None:
                    time.sleep(1)
                    cls._instance = super().__new__(cls)
            return cls._instance
    
        def __init__(self, name):
            with SingleTon._lock:
                if not SingleTon._flag:
                    time.sleep(1)
                    SingleTon._flag = True
                    self.name = name
    
    def get_instance(args):
        s = SingleTon(str(args))
        print(s, id(s))
        print(s.name)
    
    def main():
        l = []
        for i in range(3):
            l.append(threading.Thread(target=get_instance, args=(i,)))
        for i in l:
            i.start()
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    输出:

    <__main__.SingleTon object at 0x1031d8ca0><__main__.SingleTon object at 0x1031d8ca0> 4347235488 4347235488
    1
    
    <__main__.SingleTon object at 0x1031d8ca0> 4347235488
    11
    
    • 1
    • 2
    • 3
    • 4
    • 5

    完美,撒花!

    3.3 通过装饰器实现单例

    装饰器的作用是在一段逻辑之前做点事情,之后做点事情。我们可以通过这个特性去实现单例。

    3.3.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 Demo:
        a = 1
        def __init__(self, x=0):
            self.x = x
    
    d1 = Demo(1)
    d2 = Demo(2)
    print(id(d1), d1.x)
    print(id(d2), d2.x)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    输出:

    4431961824 1
    4431961824 1
    
    • 1
    • 2

    根据我们的经验,这肯定是非线程安全的。
    写个代码验证一下。

    import threading
    import time
    
    def singleton(cls):
        _instance = {}
        def _singleton(*args, **kwargs):
            if cls not in _instance:
                time.sleep(1)
                _instance[cls] = cls(*args, **kwargs)
            return _instance[cls]
        return _singleton
    @singleton
    class Demo:
        a = 1
        def __init__(self, x=0):
            self.x = x
    
    def get_instance(args):
        d = Demo(args)
        print(d.x)
    
    
    def main():
        l = []
        for i in range(3):
            l.append(threading.Thread(target=get_instance, args=(i,)))
        for i in l:
            i.start()
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    看下输出:

    01
    
    2
    
    • 1
    • 2
    • 3

    3.3.2 线程安全的装饰器单例实现

    import threading
    import time
    
    def singleton(cls):
        # 其内存储 k:v  k为类、v为实例
        _instance = {}
        _lock = threading.Lock()
        def _singleton(*args, **kwargs):
            with _lock:
                if cls not in _instance:
                    time.sleep(1)
                    _instance[cls] = cls(*args, **kwargs)
            return _instance[cls]
        return _singleton
    @singleton
    class Demo:
        a = 1
        def __init__(self, x=0):
            self.x = x
    
    def get_instance(args):
        d = Demo(args)
        print(d.x)
    
    
    def main():
        l = []
        for i in range(3):
            l.append(threading.Thread(target=get_instance, args=(i,)))
        for i in l:
            i.start()
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    看下输出:

    00
    0
    
    • 1
    • 2
  • 相关阅读:
    1500*A. Boredom(DP)
    猿创征文 | 数据结构初步(七)- 链表oj挑战
    QT 按钮的工具提示tooltips设置字体大小颜色与背景
    项目设计集合(人工智能方向):助力新人快速实战掌握技能、自主完成项目设计升级,提升自身的硬实力(不仅限NLP、知识图谱、计算机视觉等领域)
    【SemiDrive源码分析】【MailBox核间通信】52 - DCF Notify 实现原理分析 及 代码实战
    CSS的布局 Day03
    斯坦福抄袭清华、面壁智能大模型,当事人已道歉、删项目
    Git常用命令
    [深度学习]卷积神经网络的概念,入门构建(代码实例)
    ClickHouse学习笔记之SQL语句
  • 原文地址:https://blog.csdn.net/weixin_41687289/article/details/126538108