Python基础进阶6:单例模式

2024-01-08 10:33:51

你好,我是kelly,今天分享:Python单例模式,这是常见的设计模式之一。

什么是单例模式?

在Python代码运行过程中,一个类只允许创建一个实例,即内存中只存在一个类实例。

单例模式的常见使用场景:

  • 数据库连接:程序只有一个数据库连接对象

  • 配置信息:程序只有一个全局配置对象

  • 日志记录器:程序只有一个日志记录器对象

Python单例的实现方法大体有下面的5种:

  • 使用模块

  • 使用装饰器

  • 使用类

  • 基于__new__方法

  • 使用元类

平时用的最多还是第1和第4,其中第1种使用最方便,笔者也最常用,使用时不会出错。这里只介绍第1和第4种。

一、使用模块

前提知识:在Python中,模块(.py文件)天然是单例的,模块只会被加载一次。

新建design_singleton.py文件,存放单例的定义

class ToolUtil(object):
    def __init__(self, name):
        self.name = name

    def do_cupboard(self):
        print("{} 能打开柜子".format(self.name))

    def do_screw(self):
        print("{} 能拧螺丝".format(self.name))

tool_util = ToolUtil(name="工具集")

新建main.py文件,使用已定义的单例

from design_singleton import tool_util

tool1 = tool_util
tool2 = tool_util
tool3 = tool_util
print(id(tool1))
print(id(tool2))
print(id(tool3))

运行结果:

1486273127904
1486273127904
1486273127904

不同tool(1、2、3)变量的内存地址相同,说明只存在一个类实例。

二、使用__new__方法

思想:在创建类实例时,判断是否已存在类实例,若存在则直接返回,否则创建新实例。

需要用到下面2个Python魔法方法:

  • __new__:用来创建类实例(分配类实例内存空间,并返回类实例的引用(内存地址))

  • __init__:用来对类实例进行初始化赋值

根据上述思想可以得到实现代码:

class ToolUtil(object):
    _instance = None  # 类属性,保存类实例

    def __init__(self, name):
        self.name = name

    def __new__(cls, *args, **kwargs):
        # 创建类对象时不需要args和kwargs参数
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

tool1 = ToolUtil("工具集1")
print(id(tool1), tool1.name)

tool2 = ToolUtil("工具集2")
print(id(tool2), tool2.name)

tool3 = ToolUtil("工具集3")
print(id(tool3), tool3.name)

运行结果:

3049865651104 工具集1
3049865651104 工具集2
3049865651104 工具集3

不同tool(1、2、3)变量的确对应同个内存地址,内存中确实只存在一个实例。

但是,上面的实现存在2个问题:

  • 问题1:类实例会多次赋值,后一次赋值覆盖前一次赋值

  • 问题2:线程安全

问题1:类实例会多次赋值

解决方法:在对类实例初始化赋值时,进行控制。

class ToolUtilV2(object):
    _instance = None

    def __init__(self, name):
        # 对类实例,初始化赋值
        if not hasattr(self, "name"):
            self.name = name

    def __new__(cls, *args, **kwargs):
        # 创建类实例时不需要args和kwargs参数
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

tool1 = ToolUtilV2("工具集1")
print(id(tool1), tool1.name)

tool2 = ToolUtilV2("工具集2")
print(id(tool2), tool2.name)

tool3 = ToolUtilV2("工具集3")
print(id(tool3), tool3.name)

运行结果:

1737772278688 工具集1
1737772278688 工具集1
1737772278688 工具集1

类实例只会存在第一次赋值。

问题2:线程安全

解决方法:在多线程环境下存在线程安全问题,需要使用锁机制保证。

import threading

class ToolUtilV3(object):
    _lock = threading.RLock()  # 加锁,提供线程安全
    _instance = None  

    def __init__(self, name):
        if not hasattr(self, "name"):
            self.name = name

    def __new__(cls, *args, **kwargs):
        # 创建类对象时不需要args和kwargs参数
        if cls._instance is None:
            with cls._lock:
                cls._instance = super().__new__(cls)
        return cls._instance

tool1 = ToolUtilV3("工具集1")
print(id(tool1), tool1.name)

tool2 = ToolUtilV3("工具集2")
print(id(tool2), tool2.name)

tool3 = ToolUtilV3("工具集3")
print(id(tool3), tool3.name)

运行结果:

1854786707712 工具集1
1854786707712 工具集1
1854786707712 工具集1

好了,今天的分享就到这里。

本文原始版本发表链接:

https://mp.weixin.qq.com/s?__biz=MzI2Mjg3NTY5MQ==&mid=2247484753&idx=1&sn=1906c64cea9850a4b5a3cbddf2aeb84f&chksm=ea453a15dd32b3034cda4dad612c4bf004b38615e1ce656aa85e41f0f459fea20c6f98b20c87#rd

kelly会在公众号「kelly学技术」不定期更新文章,感兴趣的朋友可以关注一下,期待与您交流。

--over--

文章来源:https://blog.csdn.net/qm5132/article/details/135449120
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。