python-单例模式

单例模式

  • 单例模式
    • 单例模式的实现
      • 使用__new__实现单例模式
      • 使用装饰器实现单例模式
      • 使用import实现单例模式
      • 用指定的类方法实现单例模式
      • 创建线程安全的单例模式
    • 单例模式创建的对象内存什么时候开始释放?


单例模式

一般的类是多实例模式—即每创建一个对象,每个对象独享一份内存

class MyClass(object):
    pass

if __name__ == '__main__':
    a = MyClass()
    b = MyClass()
    print(id(a))  # 21458964135462
    print(id(b))  # 21459547139845

单例模式:保证创建对象时只创建一次。—不管你创建多少个对象,永远是同一个对象,即它们的内存地址是相同的。

单例模式的核心是对象只创建一次。

好处:防止创建多实例。这样是为了避免占用内存。

单例模式的实现

使用__new__实现单例模式

__new__方法创建单例模式的原理
如果对象已创建,就直接把创建的对象返回
如果对象未创建,创建对象,并把对象返回

class MyClass(object):
    obj = None  # 初始化类属性,用来判断是否创建对象。
    def __new__(cls, *args, **kwargs):
        # 怎么判断对象已创建呢,需要一个标识,创建一个类属性来判断。
        if cls.obj is None:
            cls.obj = super().__new__(cls)
        return cls.obj

if __name__ == '__main__':
    a = MyClass()
    b = MyClass()
    print(id(a))  # 内存地址一致为2226322707512
    print(id(b))  # 内存地址一致为2226322707512

使用装饰器实现单例模式

为什么要用装饰器来实现?
因为如果要通过__new__来实现单例模式,则每次创建类都需要对__new__方法进行重写。巧妙使用装饰器,可以避免代码冗余

装饰器实现单例模式的核心/原理是什么?
将装饰器用来装饰类,将装饰器传给类,装饰器的核心是保证创建对象时只创建一次即每次返回的都是一个对象

def singleton(cls):
    instance = {}  # { Person : Person()},字典的键为类,值为对象
    def wrapper(*args, **kwargs):  
        if not instance:
            instance[cls] = cls(*args, **kwargs)  
			# 类名加括号表示对类创建对象,并添加到instance对象字典中
        return instance[cls]  # 返回的是一个对象,通过类名为键取值
    return wrapper


@singleton
class MyClass(object):
    pass

if __name__ == '__main__':
    a = MyClass()
    b = MyClass()
    print(id(a))  # 内存地址一致,1964978897472
    print(id(b))  # 内存地址一致,1964978897472

使用import实现单例模式

import本身就是单例模式,第一次导入模块时,解释器会创建pyc文件,以后再导入的时候,直接使用的是pyc文件(不会存在导入多次创建多个对象的情况)。

原因如下:pyc是预编译的字节码文件

打开文件管理器。找到utils目录,该目录下面有个缓存文件。

pyc文件有什么用?在部署项目时有什么注意事项?
pyc是预编译的字节码文件,我们在导入一个包的时候会生成pyc文件,它的注意事项,我们在部署项目的时候一定要注意将pyc文件给清理掉,不能将它上传到服务器上。原因就是因为如果我写代码的时候使用的Windows操作系统,如果将pyc文件上传到linux服务器上,在linux服务器上执行脚本的时候会存在不兼容的情况。在linux服务器上pyc文件需要重新生成,不能直接从Windows上copy到linux服务器上。
使用Git/SVN时可以设置不上传*.pyc文件。可以保证不出现兼容性问题。

用指定的类方法实现单例模式

用指定的类方法实现单例模式的好处和缺陷是什么?
好处是可以保证单例模式和非单例模式并存。缺陷是相对复杂一些。

class MyClass(object):
    __instance = None  # 创建一个私有属性作为判断标识
    def __init__(self, *args, **kwargs):
        pass

    @classmethod  # 类方法
    def get_singleton_obj(cls, *args, **kwargs):
        if not cls.__instance:
            cls.__instance = cls(*args, **kwargs)
        return cls.__instance

if __name__ == '__main__':
    a = MyClass.get_singleton_obj()  # 调用类方法,通过类名.类方法()
    b = MyClass.get_singleton_obj()
    c = MyClass()
    d = MyClass()
    print(id(a))  # a,b是单例模式,内存地址相同
    print(id(b))
    print(id(c))  # c,d是非单例模式,内存地址不同,这样实现了单例和非单例模式共存
    print(id(d))

创建线程安全的单例模式

线程不安全好比购物时,大家同时将一个商品加入购物车(但库存告急),大家都下单付款了,实际库存不够出现的超卖现象。

线程安全的例子:

import threading

class MyClass(object):
    __instance = None
    # 创建一把锁
    __lock = threading.Lock()

    def __init__(self, *args, **kwargs):
        pass

    @classmethod
    def get_singleton_obj(cls, *args, **kwargs):
        with cls.__lock:# 下面两行在with执行结束之后就会解锁。同样的代码在锁住的时间里只会执行一次
            if not cls.__instance:#如果不加锁,在同时创建的时候,可能这10个_instance都是None,造成误判所以创建的是非单例模式
                cls.__instance = cls(*args, **kwargs)
        return cls.__instance

def creat_obj():
    obj = MyClass.get_singleton_obj()
    print(obj)

if __name__ == '__main__':
    # 1. 创建10个线程
    tasks = [threading.Thread(target=creat_obj) for i in range(10)]
    # 2. 同时启动线程
    for t in tasks:
        t.start()

单例模式创建的对象内存什么时候开始释放?

这就是为什么要少写全局变量—全局变量指的是顶级变量。只有当程序执行结束或主动执行del 变量名时,全局变量才会被销毁。
Python的垃圾回收机制来释放。一个函数执行完毕,里面引用的对象都会销毁。

关于垃圾回收相关知识,后续在深入了解。

你可能感兴趣的:(python,python,单例模式,开发语言)