Python 单例模式的几种实现方式


单例模式的几种实现方式

先来看几个魔法方法的简单运用:__new__, __init__, __call__。

class A(object):
    def __init__(self, x):
        print('x in __init__', x)

    def __new__(cls, y):  # 它只取下 cls 参数,并把其他参数传给 __init__
        print('y in __new__', y)
        return super(A, cls).__new__(cls)

    def __call__(self, z):  # 允许一个类的实例像函数一样被调用
        print('z in __call__', z)

A('123')('abc')  # 相当于先 a = A('123'), 再 a('abc')
# y in __new__ 123
# x in __init__ 123
# z in __call__ abc
1._new_(cls, *args, **kw) 方法实现

__new__ 构造方法至少需要一个 cls 参数,实例化时,解释器会自动填入;
需要注意的是,new 方法中调用 new 方法时不要再调用自己的 new 方法,会报【超出最大递归深度】错误;要调用父类的 new 方法(默认调用)。

class Singleton(object):    
    _instance = None  # 新建一个类属性:需要使用类名前缀来访问
    
    def __new__(cls, *args, **kw):  # 创建实例时,判断是否已经存在,存在,返回,不存在,创建
        if cls._instance is None:
            cls._instance = object.__new__(cls, *args, **kw)  # 不要调用自己的new方法, 即: cls.__new__(), 会无限递归的; 
            
        return cls._instance  # 返回实例对象
        
    def __init__(self):
        pass
    
sing1 = Singleton()
sing2 = Singleton()
sing1 is sing2  # true

"""
若 _instance = {}, 则
if cls not in cls._instance:
    cls._instance[cls] = object.__new__(cls, *args, **kw)
"""
2.函数装饰器实现:实例化前先调用这个装饰器返回的 inner() 函数,再返回实例

用不可变的 类地址 作为键,其 实例 作为值,每次创造实例时,首先判断该类是否存在实例,存在,直接返回该实例即可,否则新建一个实例并存放在字典中。

def singleton2(cls):
    _instance = {}  # 新建空字典; 不要_instance = None, 否则 inner 函数中赋值时,_instance 只会在函数作用域搜索,判断时会报错;
    
    def inner():  # 判断是否已有实例,如无,则新建一个实例并返回
        if cls not in _instance:
            _instance[cls] = cls()

        return _instance[cls]  # 返回的是实例对象
    
    return inner

@singleton2
class Cls():
    def __init__(self):
        pass  # 可以照常添加属性和方法
    
cls1 = Cls()
cls2 = Cls()
cls1 is cls2  # True
3.类装饰器实现:类装饰器可以直接依靠类内部的__call__方法来实现

Python 中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象。可调用对象包括自定义的函数、Python 内置函数以及类实例对象。
__call__ 方法可以使类实例对象可以像调用普通函数那样,以 “对象名()” 的形式使用;此方法会在实例作为一个函数被 “调用” 时被调用;
如果定义了此方法,则 x(arg1, arg2, ...) 就相当于 x.__call__(arg1, arg2, ...) 的快捷方式。只要实现了__call__方法的对象都是可被调用对象。

class Singleton3(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]  # 返回实例对象

    
@Singleton3
class Cls2(object):
    def __init__(self):
        pass
    
cls3 = Cls2()
cls4 = Cls2()
cls3 is cls4  # True


class Cls3():
    pass

Cls33 = Singleton3(Cls3)  # Cls3 传给 __init__ 了;

cls5 = Cls33()  # “名称()” 可以理解为是 “名称.__call__()” 的简写; 
cls6 = Cls33()
cls5 is cls6  # True
4.metaless 元类实现

元类->类->实例,简单来说,就是类是由元类创建的,type 是内置的元类,也可以自定义元类。
type(name, bases, dict):name 是类的名字,bases 是要继承的父类集合,dict 是这个类的属性。下面两条语句会创建相同的 type 对象:
class X: a = 1
X = type('X', (object,), dict(a=1)) # 动态的创建类对象,dict(a=1) 也可以直接用 {'a':1, 'func_name': func_name}

class Singleton4(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton4, cls).__call__(*args, **kwargs)

        return cls._instances[cls]

class Cls4(metaclass=Singleton4):
    pass

cls1 = Cls4()
cls2 = Cls4()
cls1 is cls2  # True
5.import 实现

python 模块是天然的单例模式:需要使用实例的时候,在文件中导入即可使用,从而减少相关实例的创建;
模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码;
因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。如果我们真的想要一个单例类,可以考虑这样做:

# my_singleton.py
class My_Singleton(object):
	def foo(self):
		print('My_Singleton')
				
mysingle = My_Singleton()

# other.py
from my_singleton import mysingle

mysingle.foo()
6.共享属性

创建实例时, 把所有实例的 __dict__ 都指向同一个字典,这样它们就会共享同一个属性,具有相同的属性和方法。
要查看对象中存储的所有值,可检查其 __dict__ 属性。如果要确定对象是由什么组成的,应研究模块 inspect。
注意:此时只是对象值相同,多个对象共享一个数据,依旧以最新的值为准,但不是同一个对象。

class Borg(object):
    _state = {}  # 共享属性字典
    
    def __new__(cls, *args, **kw):
        ob = super(Borg, cls).__new__(cls, *args, **kw)
        ob.__dict__ = cls._state  # 指向同一个字典
        
        return ob

    
class MyClass2(Borg):
    a = 1
应用场景简介

每个实例都会占用资源,而且实例初始化会影响性能,这个时候就可以考虑使用单例模式,它给我们带来的好处是只有一个实例占用资源,并且只需初始化一次;
当有同步需要的时候,可以通过一个实例来进行同步控制,比如对某个共享文件(如日志文件)的控制,对计数器的同步控制等,这种情况下由于只有一个实例,所以不用担心同步问题。
模块 logger 就是一个单例模式;Windows的资源管理器是一个单例模式;线程池,数据库连接池等资源池一般也用单例模式;网站计数器;游戏场景管理器;
游戏中需要有 “场景管理器” 这样一种东西,用来管理游戏场景的切换、资源载入、网络连接等等任务。
这个管理器需要有多种方法和属性,在代码中很多地方会被调用,且被调用的必须是同一个管理器,否则既容易产生冲突,也会浪费内存资源。
某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,
也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。