深入思考Python单例模式

132 阅读4分钟

启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第38天,点击查看活动详情

在面向对象编程过程当中,绕不开的一个话题就是类定义和实例化,众所周知,类的每个实例拥有独立的内存空间,但是在一些使用场景下,反而需要只保留一个实例,比如:全局配置文件,这个时候就用到了单例模式。

单例模式

单例模式,就是在一个脚本当中,无论如何调用,都只会形成一个实例,透过现象看本质,其实就是在保证多次实例化操作的是同一块内存,自然使用场景就是哪些多个地方需要实例化,又需要数据一致,比如配置文件,可以在项目的各个地方实例化修改,但是维护的应该是一个配置文件才可以。

基于Python实现单例模式

基于上面的讲解,我们来尝试使用Python实现单例模式,解析上面的描述就是保证类实例化指向的是一个实例,所以我们大概思考了三种实现思路:

基于导入实现单例模式

这种方式特别适合配置文件,把一个类写道一个文件当中,并且在文件当中使用类名进行实例化,那么之后其他文件导入使用的时候,由于类名已经被实例名覆盖,所以导入的就是同一个实例好的实例,也就实现了单例模式

定义单例文件

class Singleton(object):
    def __init__(self):
        pass
​
    def __call__(self, *args, **kwargs):
        print(id(self))
​
Singleton = Singleton()

导入使用

from Singleton import Singleton
​
Singleton()
Singleton()

基于装饰器实现

这里利用函数装饰器定义了一个固定的容器_instance,将常规实例化的步骤改成了经过装饰器实例化,在转身去当中实例化需要先判断_instacnce字典当中是否有以类作为的键,如果有就返回这个键对应的值,如果没有就实例化类,作为这个键的值,然后返回,所以这样实现了单例模式。

def singleton(cls):
    """
    :param cls: 代表的是类
    :return: 返回一个实例
    """
    _instance = {} #作为一个容器来存储类实例
    def inner(*args,**kwargs):
        if cls not in _instance: #判断类在不在容器当中
            _instance[cls] = cls(*args,**kwargs) #如果不在使用类做键,实例做值传入容器,之后再有实例化,由于类对象没有边,所以会返回之前实例化的实例,这里利用了装饰器共享_instance实现的单例模式
        return _instance[cls]
    return inner
​
@singleton
class Dome(object):
    def __call__(self, *args, **kwargs):
        print(id(self))
​
d1 = Dome()
d2 = Dome()

采用__new__方法实现

__new__方法时Python面向对象的一个魔术方法,在析构函数之前自动执行,可以有返回值,我们采用采用new方法来创建实例,将实例绑定到固定的类属性上,由于类是独立的内存空间(这里一定要区分清楚类变量和实例变量),每次实例化多可以访问到类的内存空间,所以实例化由之前的创建一个实例变成了类变量_instance是否有值,有值就返回值,没有值就指向一个实例,然后返回值,之后再次实例化,由于类的内存空间没有变,类变量还是指向第一次形成的实例。

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if cls._instance is None: #判断类实例是否有值
            cls._instance = super().__new__(cls) #没有值传入一个实例
        return cls._instance
​
    def __call__(self, *args, **kwargs):
        print(id(self))
​
s1 = Singleton()
s2 = Singleton()
s1()
s2()
​

基于单例模式的思考

单例模式的一些场景实际上关注的是多个实例是否操作的是同一个对象,那么这种情况下,是不是一个实例真的重要吗,假如我们可以多个不同的实例操作一个内存空间是不是也可以呢,比如:

class Singleton(object):
    _share = {}
    def __new__(cls, *args, **kwargs):
        obj = object.__new__(cls)
        obj.__dict__ = cls._share
        return obj
​
class SingDome(Singleton):
    def __init__(self,name=None):
        if name:
            self.name = name
    def __str__(self):
        return "这是 %s"%self.name
​
sing1 = SingDome("老张")
sing2 = SingDome()
​
print(id(sing1))
print(id(sing2))
​
print(sing1)
print(sing2)

这里采用继承的方式,在父类当中将实例化发步骤分成了两个步骤:

1、使用基类(object)创建实例

2、将实例的所有属性(dict)备份到父类的_share容器当中

这样之后字类的所有属性都在_share容器当中,自然,_share称为了共享的部分,但是查看实例的id,可以看到他们并不是一个实例,所以也称不上单例模式,但是具有同样的效果,基于单例模式我们大概进行了这样的思考,还请各位大佬多多指点。