• Python:用于有效对象管理的单例模式


    1. 写在前面

    在本文中,我们将介绍一种常用的软件设计模式 —— 单例模式。

    通过示例,演示单例创建,并确保该实例在整个应用程序生命周期中保持一致。同时探讨它在 Python 中的目的、益处和实际应用。

    关键点:

    1、单例模式只有一个实例存在;
    2、单例模式必须自己创建自己的唯一实例;
    3、单例模式是一种软件设计模式,而不是专属于某种编程语言的语法;
    
    • 1
    • 2
    • 3

    公众号: 滑翔的纸飞机

    现在让我们开始吧!

    2. Python 单例模式

    那什么是单例模式?

    这是个非常简单的问题,基本上就是当我们有一个类时,我们只能实例化该类的一个实例对象,无论你是要优化资源使用、配置数据,还是要为我们的程序优化某个全局只读数据,该模式都提供了一个清晰有效的解决方案。

    2.1 实现

    因为有不同的方式可以创建。这里我将向你展示几种简单的方法。

    2.1.1 使用 __init__

    在这里,我将定义一个 Singleton 类并定义一个方法getInstance()。我们的想法是,无论我在哪里调用,它都会返回 Singleton.getInstance()的特定实例。

    考虑,无论是否已创建类实例,都将返回特定实例对象。因此,在这里使用类变量,跟踪实例是否已创建。

    class Singleton:
    
      # 类变量 __instance 将跟踪唯一的对象实例
      __instance = None
    
      @staticmethod
      def getInstance():
        if Singleton.__instance == None:
          Singleton()
        return Singleton.__instance
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    现在看起来当然还不是一个单例,接下去通过__init__()方法(类似于构造函数),在Singleton对象实例化时被调用。该对象会将类变量设置为对象实例。__init__()全局只应被调用一次,并且由该类中的方法调用(getInstance()),保证__instance类变量赋值之后不允许再此赋值。

    def __init__(self):
      if Singleton.__instance != None:
         raise Exception("Singleton object already created!")
      else:
         Singleton.__instance = self
    
    • 1
    • 2
    • 3
    • 4
    • 5

    现在,让我们创建实例验证:
    在s1 情况下会调用__init__()创建实例;
    在s2 情况下返回与s1相同实例;

    s1 = Singleton.getInstance()
    print(s1)
    s2 = Singleton.getInstance()
    print(s2)
    
    • 1
    • 2
    • 3
    • 4

    而且如果我们在 s1 上设置属性,s2 也会有相同的值,因为它们都指向同一个对象。

    s1.x = 5
    print(s2.x)
    
    • 1
    • 2

    它将打印以下输出

    <__main__.Singleton object at 0x10e24fdf0>
    <__main__.Singleton object at 0x10e24fdf0>
    5
    
    • 1
    • 2
    • 3

    完整代码示例:

    """
    @Time:2023/9/15 01:18
    @Author:'jpzhang.ht@gmail.com'
    @Describe:
    """
    
    class Singleton:
    
        # 类变量 __instance 将跟踪唯一的对象实例
        __instance = None
    
        def __init__(self):
            if Singleton.__instance != None:
                raise Exception("Singleton object already created!")
            else:
                Singleton.__instance = self
    
        @staticmethod
        def getInstance():
            if Singleton.__instance == None:
                Singleton()
            return Singleton.__instance
    
    
    if __name__ == '__main__':
        s1 = Singleton.getInstance()
        print(s1)
        s2 = Singleton.getInstance()
        print(s2)
        s1.x = 5
        print(s2.x)
    
    • 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

    2.1.2 使用 __call__ & metaclass

    __call__():Python中,只要在创建类时定义了__call__()方法,这个类就是可调用对象。

    针对__call__()使用参考示例:

    class Father:
        def __call__(self):
            print('My call() function')
    
    fat=Father()
    fat()
    
    输出:My call() function
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    很神奇不是,实例对象也可以像函数一样作为可调用对象来用。

    接下去通过__call__实现单例,完整代码示例如下:

    """
    @Time:2023/9/15 01:26
    @Author:'jpzhang.ht@gmail.com'
    @Describe:
    """
    
    
    class Singleton(type):
        _instances = {}
    
        def __call__(cls, *args, **kwargs):
            if cls not in cls._instances:
                cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
            return cls._instances[cls]
    
    
    class Singleton1(metaclass=Singleton):
        pass
    
    
    
    if __name__ == '__main__':
        s1 = Singleton1()
        print(s1)
        s2 = Singleton1()
        print(s2)
        s1.x = 5
        print(s2.x)
    
    
    • 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__.Singleton1 object at 0x1120c8280>
    <__main__.Singleton1 object at 0x1120c8280>
    5
    
    • 1
    • 2
    • 3

    2.1.3 使用 __new__

    简单介绍下__new__():只要是面向对象的编程语言,类的实例化都一定包含两个步骤:

    (1)在内存中创建对象,即开辟一块内存空间来存放类的实例(Instance);
    (2)初始化对象,即给实例的属性赋予初始值,例如全部填空;

    在 python 中,第一步由 __new__ 函数负责,第二步由 __init__ 函数负责。

    在这种方法中,我们将使用__new__(),让它检查类变量以查看实例是否已创建,如果没有,我们就创建它,无论是否需要创建,我们都会返回实例。

    class Singleton:
    
        # 类变量 __instance 将跟踪唯一的对象实例
        __instance = None
    
        def __new__(cls):
            if (cls.__instance is None):
                cls.__instance = super(Singleton, cls).__new__(cls)
        
            return cls.__instance
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    因此,当第一次创建单例对象时,它会运行__new__,当判断__instance为 None,则创建对象并赋值至__instance类变量。下次运行时,发现__instance不是 None 已被设置,于是将返回 Singleton.__instance

    这里通过调用超类的方法来实际返回对象实例本身,然后再返回它的__new__。

    s1 = Singleton()
    print(s1)
    s2 = Singleton()
    print(s2)
    
    # 同样,如果在 s1 上设置一个属性,s2将具有相同的值,因为它们都引用同一个对象
    s1.x = 5
    print(s2.x)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    它将打印以下输出

    <__main__.Singleton object at 0x10607beb0>
    <__main__.Singleton object at 0x10607beb0>
    5
    
    • 1
    • 2
    • 3

    完整示例:

    """
    @Time:2023/9/16 23:04
    @Author:'jpzhang.ht@gmail.com'
    @Describe:
    """
    
    
    class Singleton:
        # 类变量 __instance 将跟踪唯一的对象实例
        __instance = None
    
        def __new__(cls):
            if (cls.__instance is None):
                cls.__instance = super(Singleton, cls).__new__(cls)
    
            return cls.__instance
    
    
    if __name__ == '__main__':
        s1 = Singleton()
        print(s1)
        s2 = Singleton()
        print(s2)
    
        # 同样,如果在 s1 上设置一个属性,s2将具有相同的值,因为它们都引用同一个对象
        s1.x = 5
        print(s2.x)
    
    • 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

    2.1.4 装饰器

    关于装饰器读者自行查阅其他博文;

    本例照旧通过 __instance = {} 来跟踪实例对象。使用类地址作为键,实例作为值,每次创造实例时,检查该类是否存在实例,存在的话直接返回该实例即可,否则新建一个实例并存放在字典中。

    函数装饰器示例:

    """
    @Time:2023/9/16 23:29
    @Author:'jpzhang.ht@gmail.com'
    @Describe:
    """
    
    
    def singleton(cls):
        __instance = {}
    
        def inner():
            if cls not in __instance:
                __instance[cls] = cls()
            return __instance[cls]
    
        return inner
    
    
    @singleton
    class Cls(object):
        def __init__(self):
            print('My name Cls')
    
    
    if __name__ == "__main__":
        cls1 = Cls()
        print(cls1)
        cls1.x = 5
        cls2 = Cls()
        print(cls2)
        print(cls2.x)
    
    
    • 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

    输出:

    My name Cls
    <__main__.Cls object at 0x10f284160>
    <__main__.Cls object at 0x10f284160>
    5
    
    • 1
    • 2
    • 3
    • 4

    类装饰器示例:

    """
    @Time:2023/9/16 23:39
    @Author:'jpzhang.ht@gmail.com'
    @Describe:
    """
    
    
    class Singleton(object):
        def __init__(self, cls):
            self._cls = cls
            self._instance = {}
    
        def __call__(self):
            if self._cls not in self._instance:
                self._instance[self._cls] = self._cls()
            return self._instance[self._cls]
    
    
    @Singleton
    class Cls(object):
        def __init__(self):
            print('My name Cls')
    
    
    if __name__ == "__main__":
        # print('=======使用1=======')
        cls1 = Cls()
        print(cls1)
        cls1.x = 5
        cls2 = Cls()
        print(cls2)
        print(cls2.x)
    
        # print('=======使用2=======')
        # cls1 = Singleton(Cls)
        # cls2 = Singleton(Cls)
        # print(cls1)
        # print(cls2)
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38

    输出:

    My name Cls
    <__main__.Cls object at 0x10ede0850>
    <__main__.Cls object at 0x10ede0850>
    5
    
    • 1
    • 2
    • 3
    • 4

    3. 使用案例

    单例经常与设计模式结合使用,以解决最常见的问题,包括缓存、日志、配置设置和线程池。

    案例1:

    """
    @Time:2023/9/16 23:09
    @Author:'jpzhang.ht@gmail.com'
    @Describe:
    """
    
    
    class Logger:
        _instance = None
    
        def __new__(cls):
            if cls._instance is None:
                cls._instance = super(Logger, cls).__new__(cls)
                cls._instance._initialize()
            return cls._instance
    
        def _initialize(self):
            self.log_file = open("log.txt", "a")
    
        def log(self, message):
            self.log_file.write(message + "\n")
            self.log_file.flush()
    
        def close(self):
            self.log_file.close()
    
    
    class LoggerFactory:
        def create_logger(self):
            return Logger()
    
    
    # 使用方法
    if __name__ == "__main__":
        logger_factory = LoggerFactory()
    
        logger1 = logger_factory.create_logger()
        logger2 = logger_factory.create_logger()
    
        logger1.log("This is a log message from logger1")
        logger2.log("This is a log message from logger2")
    
        logger1.close()
        logger2.close()
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    在本例中,创建了一个采用单例模式的日志类Logger。它确保只创建一个类实例。LoggerFactory类是一个工厂类,可创建Logger类的实例。

    log.txt:
    
    This is a log message from logger1
    This is a log message from logger2
    
    • 1
    • 2
    • 3
    • 4

    运行代码后,可以看到两个对象(logger1、logger2),实际是Logger类的同一个实例。这样可以确保日志记录只使用一个日志文件。

    案例2:

    线程安全是实现单例模式时的一个重要考虑因素,因为多个线程可能会尝试同时访问或创建类的实例。如果没有适当的同步,这可能会导致创建多个实例,从而违反单例原则。

    import threading
    import random
    
    
    class Singleton:
        _instance = None
    
        def __init__(self):
            self.value = random.randint(1, 10)
    
        def __new__(cls):
            if cls._instance is None:
                cls._instance = super(Singleton, cls).__new__(cls)
            return cls._instance
    
    
    def create_singleton(index):
        s = Singleton()
        print(f"Singleton instance created by thread {threading.current_thread().name}: {s} and value: {s.value}\n")
    
    
    # 模拟多个线程同时创建单例
    def problem_case():
        threads = []
        for i in range(5):
            thread = threading.Thread(target=create_singleton, args=(i,))
            threads.append(thread)
            thread.start()
    
        for thread in threads:
            thread.join()
    
    # 使用锁来确保线程安全
    class ThreadSafeSingleton:
        __instance = None
        __lock = threading.Lock()
    
        def __init__(self):
            self.value = random.randint(1, 10)
    
        @classmethod
        def get_instance(cls):
            if not cls.__instance:
                with cls.__lock:
                    if not cls.__instance:
                        cls.__instance = cls()
            return cls.__instance
    
    
    def create_thread_safe_singleton(index):
        s = ThreadSafeSingleton.get_instance()
        print(f"Singleton instance created by thread {threading.current_thread().name}: {s} and value: {s.value}\n")
    
    
    def thread_safe_case():
        threads = []
        for i in range(5):
            thread = threading.Thread(target=create_thread_safe_singleton, args=(i,))
            threads.append(thread)
            thread.start()
    
        for thread in threads:
            thread.join()
    
    
    if __name__ == "__main__":
        print("Problem case (without thread safety):")
        problem_case()
    
        print("\nThread-safe case:")
        thread_safe_case()
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    在上例中,ThreadSafeSingleton 使用线程锁类创建了一个锁对象,我们可以用它来同步对类变量的访问。然后定义一个类方法,首先检查是否已经创建了该类的实例。如果没有,它会获取锁,并创建该类的新实例。

    备注:获取锁后再次校验是否已经创建了该类的实例。

    输出:

    Problem case (without thread safety):
    Singleton instance created by thread Thread-10: <__main__.Singleton object at 0x102881760> and value: 4
    
    Singleton instance created by thread Thread-7: <__main__.Singleton object at 0x102881760> and value: 3
    
    Singleton instance created by thread Thread-6: <__main__.Singleton object at 0x102881760> and value: 10
    
    Singleton instance created by thread Thread-8: <__main__.Singleton object at 0x102881760> and value: 9
    
    Singleton instance created by thread Thread-9: <__main__.Singleton object at 0x102881760> and value: 4
    
    
    Thread-safe case:
    Singleton instance created by thread Thread-11: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2
    
    Singleton instance created by thread Thread-12: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2
    
    Singleton instance created by thread Thread-13: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2
    
    Singleton instance created by thread Thread-14: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2
    
    Singleton instance created by thread Thread-15: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    不难看出,上锁后符合单例原则。

    3. 最后

    实现该模式的每种方法都有自己的优缺点,选择哪种方法可能取决于具体的用例。虽然该模式在某些情况下很有用,但它也可能带来一些缺点,例如使代码更难测试和维护。

    总之,单例设计模式是管理应用程序中类的单个实例的强大工具。不过,在使用时应谨慎,考虑系统的具体要求以及对可维护性、可测试性和并发性的潜在影响。

    感谢您花时间阅读文章
    关注公众号不迷路:)
  • 相关阅读:
    关于AES加密输出密文不为128位的倍数的原因
    java计算机毕业设计共享汽车管理系统MyBatis+系统+LW文档+源码+调试部署
    最长上升子序列(acwing 895 acwing 896 acwing1017)
    LeetCode_模拟_中等_498.对角线遍历
    电脑使用技巧
    【计算机网络实验】TCP和UDP传输过程仿真与分析
    c++征途 --- 项目 --- 职工管理系统
    vue3接入腾讯地图后遇到的错位问题探究
    SpringBoot项目使用JSP
    随机森林R语言预测工具
  • 原文地址:https://blog.csdn.net/u011521019/article/details/133003644