欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python中的单例模式以及一些坑

程序员文章站 2022-06-03 18:59:08
...

一、单例模式的概述:

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

从具体实现角度来说,就是以下三点:一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,三是该类提供了一个静态的共有函数用于创建或获取它本身的静态私有对象。

二、应用:

一些资源管理器常常设计成单例模式

在计算机系统中,需要管理的资源包括软件外部资源,譬如每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印机作业同时输出到打印机中。每台计算机可以有若干传真机卡,但是只应该有一个软件负责管理传真卡,以避免一个通信端口同时被两个请求同时调用。

需要管理的资源包括软件内部资源,譬如,大多数的软件都有一个(甚至多个)属性(properties)文件存放系统配置。这样的系统应当由一个对象来管理一个属性文件。

需要管理的软件内部资源也包括负责记录网站来访人数的部件,记录软件系统内部事件、出错信息的部件,或是对系统的表现进行检查的部件等。其一,这些资源管理器构件必须只有一个实例;其二,它们必须自行初始化;其三,允许整个系统访问自己。因此,它们都满足单例模式的条件,是单例模式的应用。

三、单例模式的优缺点:

优点:

1、实例控制

单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。

2、灵活性

因为类控制了实例化过程,所以类可以灵活更改实例化过程。

缺点:

1、开销

虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。

2、可能的开发混淆

使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。

3、对象生成期

不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致单例类中出现悬浮引用。

四、在Python中,单例模式有以下三种实现方式。

        1. 重写__new__方法:通过判断是否存在实例决定是否创建新对象,这种方式最方便理解,但问题最大。

class Singleton(object):

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls,'_instance'):
            orig=super(Singleton,cls)
            cls._instance=orig.__new__(cls,*args,**kwargs)

        return cls._instance

        2.继承 type 类型重写__call__。自定义类型重写__call__方法实现单例模式,最难理解。

class Singleton_Meta(type):

    def __init__(self, name, bases, dict):
        super(Singleton_Meta,self).__init__(name,bases, dict)
        self._instance = None

    def __call__(self, *args, **kwargs):
        if self._instance is None:
            self._instance = super(Singleton_Meta,self).__call__(*args, **kwargs)

        return self._instance

        3.使用装饰器(decorator)。直接从外部判断,与类无关,隔离性最好。

def singleton(cls, *args, **kwargs):
    instances = {}
    
    def _singleton():
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return _singleton

五、以上三种单例模式使用方法和对比。

        1. 重写__new__方法

class Singleton(object):

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls,'_instance'):
            orig=super(Singleton,cls)
            cls._instance=orig.__new__(cls,*args,**kwargs)

        return cls._instance


#TestClass 的 __init__ 与 __new__ 方法中创建的过程依然会执行,不知不觉就有了隐患。
class TestClass(Singleton):
    def __init__(self) -> None:
        print("init")
        super().__init__()

    def __new__(cls, *args, **kwargs):
        print("new")
        return super().__new__(cls, *args, **kwargs)


#这个问题在 logging 模块中尤为明显。
class Log(Singleton):

    def __init__(self) -> None:

        self.logger = logging.getLogger("")
        # 获取文件路径
        logpath = str(tester.config["LOG_PATH"])
        # 创建目录
        Path(logpath).parent.mkdir(exist_ok=True)
        # 创建文件句柄
        rotatingFileHandler = RotatingFileHandler(filename=logpath,
                                                  maxBytes=1024 * 1024 * 50,
                                                  backupCount=5,
                                                  encoding="utf8")

        # 控制台句柄
        console = logging.StreamHandler()
        console.setLevel(tester.config["LOG_LEVEL"])
        console.setFormatter(formatter)

        # 添加handler
        self.logger.addHandler(rotatingFileHandler)
        self.logger.addHandler(console)
        self.logger.setLevel(tester.config["LOG_LEVEL"])

    def info(self, message):
        self.logger.info(message)


def test():
    Log();Log();
    log =Log()
    log.info("abc")
    #此时会发现控制台与日志文件中都输出了三次 abc
    #原因在于 __init__ 中对同样的logger加入了三次handler

    

        2.继承 type 类型重写__call__。这种方法能避免掉上述bug。

class Singleton_Meta(type):

    def __init__(self, name, bases, dict):
        super(Singleton_Meta,self).__init__(name,bases, dict)
        self._instance = None

    def __call__(self, *args, **kwargs):
        if self._instance is None:
            self._instance = super(Singleton_Meta,self).__call__(*args, **kwargs)

        return self._instance

class TestClass(metaclass=Singleton_Meta):
    pass

 3.使用装饰器(decorator)。这种方法能避免掉上述bug。

def singleton(cls, *args, **kwargs):
    instances = {}
    
    def _singleton():
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return _singleton

@singleton
class TestClass:
    pass