Python下实现单例模式

178 阅读4分钟

综述

单例模式是一种很常见的设计模式,一个类只允许创建一个对象(或者实例),那这个类就是一个单例类

原文链接

更多内容欢迎关注我的个人博客,WindTrack

__new__方法

通常我们把__init__称作构造方法,但实际构造实例用的是__new__方法,这是一个特殊的类方法,不需要使用@classmethod装饰器,必须返回一个实例,返回的实例会作为第一个参数(即self)传给__init__方法,而__init__方法调用时需要传入实例,且禁止任何返回值,因此__init__方法其实是初始化方法,真正的构造方法实际是__new__

__new__方法也可以返回其它类的实例,这样就不会调用__init__方法

class A(object):
    def __init__(self, *args, **kwargs):
        print('init')
        self.a = 'b'
        print('init end')
        
    def __new__(cls, *args, **kwargs):
        print('new')
        return super().__new__(cls)
      
c = A(c='d')

可以看到,这里的__new__方法会调用父类__new__方法,实际返回还是一个本身的实例,所以会正常调用__init__方法,结果如下

new
init
init end

如果__new__方法不返回本身的实例,则不会去调用自身的__init__方法

class A(object):
    def __init__(self, *args, **kwargs):
        print('init')
        self.a = 'b'
        print('init end')
        
    def __new__(cls, *args, **kwargs):
        print('new')
        return 'a'
#         return super().__new__(cls)

c = A(c='d')
c

结果可以看到,未调用__init__方法,同时实例化后返回的结果是字符串a

new
'a'

使用__new__方法实现单例

我们可以修改__new__方法,增加一个类属性

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
#             orig = super(Singleton, cls)
#             cls._instance = orig.__new__(cls)
            cls._instance = super().__new__(cls)
        return cls._instance
    
class MyClass(Singleton):
    a = 1
    def __init__(self, a, *args, **kargs):
        self.aaa = a

尝试调用

a = MyClass('b')
print('a', a.aaa, id(a))

b = MyClass('c')
print('b', b.aaa, id(b))

print('new', a.aaa, id(a))

结果

a b 4467388368
b c 4467388368
new c 4467388368

可以看到,当初始化实例b后,实例a的属性aaa也变成了和实例b一样的c,同时实例a和实例b指向同一个地址

其中在调用原始__new__方法时,可以用注释中的代码,也就是

orig = super(Singleton, cls)
cls._instance = orig.__new__(cls)

具体super方法的用法暂时不讨论

使用共享属性实现单例

实例的属性和方法都在__dict__中,只要将所有实例的__dict__方法指向同一个字典,这样他们就具有了相同的属性和方法

class Borg(object):
    _state = {}
    def __new__(cls, *args, **kwargs):
        ob = super().__new__(cls)
        ob.__dict__ = cls._state
        return ob
    
class BorgClass(Borg):
    a = 1
    def __init__(self, a, *args, **kargs):
        self.aaa = a    

尝试调用

a = BorgClass('b')
print('a', a.aaa, a.__dict__, id(a), id(a.__dict__))

b = BorgClass('c')
print('b', b.aaa, b.__dict__, id(b), id(b.__dict__))

print('new', a.aaa, a.__dict__, id(a), id(a.__dict__))

结果

a b {'aaa': 'b'} 4467362560 4467142232
b c {'aaa': 'c'} 4467363288 4467142232
new c {'aaa': 'c'} 4467362560 4467142232

可以看到,当初始化实例b后,实例a的属性aaa也变成了和实例b一样的c,同时,我们也可以看到,实例a和实例b并不是同一个实例,但是他们的__dict__指向是同一个地址

使用装饰器实现单例

使用装饰器时,可以设置一个字典,根据cls来取对应的实例,这种方法不需要重写__new__方法

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


@singleton
class DecoraterClass(Borg):
    a = 1
    def __init__(self, a, *args, **kargs):
        self.aaa = a

尝试调用

a = DecoraterClass('b')
print('a', a.aaa, id(a))

b = DecoraterClass('c')
print('b', b.aaa, id(b))

print('new', a.aaa, id(a))

结果

a b 4467306224
b b 4467306224
new b 4467306224

可以看到,当初始化实例b后,实例a的属性aaa也变成了和实例b一样的c,同时,实例a和实例b指向也是同一个地址

使用import方法支持单例

在Python中,模块是天然的单例模式

在文件import_singleton.py中定义

# import_singleton.py
class ImportSingleton(object):
    a = 1
    def __init__(self, a, *args, **kargs):
        self.aaa = a

import_singleton_instance = ImportSingleton('a')

在另一个文件中引用

from import_singleton import import_singleton_instance

这样在多个模块里import得到的都是同一个对象

需要注意的是,如果原模块这样写,

# import_singleton.py
class ImportSingleton(object):
    a = 1
    def __init__(self, a, *args, **kargs):
        self.aaa = a

import_singleton_instance = ImportSingleton

这样在其他模块中初始化得到的并不是同一个对象