乐趣区

关于python:单例模式的五种设计方案

单例模式是一种罕用的开发设计模式,它的次要目标就是确保只有一个实例对象的存在,也就是说当你心愿一个类的性能比拟繁多,你只须要一个实例对象就能够实现须要的时候,就能够应用单例模式,以此来节俭内存资源。

比方咱们在 web 开发我的项目中,咱们常常须要做到的一个性能就是应用单例模式来开发短信验证码这个性能。咱们通常是应用通信产品来发送短信验证码,那么咱们就只须要用一个实例对象去实现这个短信发送的性能就能够了。

1. 模块实现单例模式

大家应该都晓得,模块的导入只可能让被导入程序执行一次,你屡次导入也只会执行一次,那么咱们能够说模块就是一个人造的单例模式,因而,咱们只需把相干函数和数据定义在一个模块中,应用的时候将它导入到其余模块应用就能够达到单例模式的成果了。

single.py
···
class Singleton(object):
    def foo(self):
        pass
    print(1)
singleton = Singleton()

from singleton import singleton
from singleton import singleton
执行后果:1 

2. 应用装璜器实现单例模式

装璜器的作用置信大家都是晓得的,能够给咱们别的函数或者类增加一个性能,那么咱们同样的能够给类写一个逻辑,让类只能生成一个实例对象。

def Singleton(cls):
    _instance = {}
    def singleton(*args, **kargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kargs)
        return _instance[cls]
    return singleton

@Singleton
class A(object):
    a = 1
    def __init__(self, x=0):
        self.x = x

a1 = A(2)
a2 = A(3)
print(a1)
print(a2)
执行后果:<__main__.A object at 0x0000027432DABE48>
<__main__.A object at 0x0000027432DABE48> 

3. 应用类办法实现单例模式

当咱们实用类间接创立实例对象的时候,创立的并不是单例对象,那么咱们须要在类中定义一个类办法来编辑逻辑实现单例模式,次要思路就是会去判断类是否有_instance 这个属性,如果有则间接返回这个实例,没有则创立实例。

class Singleton(object):
    def __init__(self,*args,**kwargs):
        pass

    @classmethod
    def get_instance(cls, *args, **kwargs):
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance

s1 = Singleton.get_instance()
s2 = Singleton.get_instance()
print(s1)
print(s2)
执行后果:<__main__.Singleton object at 0x0000017B0359B550>
<__main__.Singleton object at 0x0000017B0359B550> 

然而这样去实现单例模式有个隐患,当咱们退出多线程去创立实例对象的时候,咱们的执行速度够快还不会呈现影响,当执行速度不够快的时候,一个线程去创立实例而后拿到了_instance 这个属性去判断,其余的线程可能也同时会拿到这个_instance 这个属性,发现并没有实例存在,所以这两个线程就会同时创立一个实例,就会造成创立了多个实例的景象,咱们的单例模式天然也就生效了。举例来说明,咱们这里间接加上这个休息时间演示执行慢的状况。

import threading
import time

class Singleton(object):
    def __init__(self, *args, **kwargs):
        time.sleep(1)

    @classmethod
    def get_instance(cls, *args, **kwargs):
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton(*args, **kwargs)

        return Singleton._instance

def task(arg):
    obj = Singleton.get_instance(arg)
    print(obj)

for i in range(10):
    t = threading.Thread(target=task, args=[i,])
    t.start()
执行后果:<__main__.Singleton object at 0x000001774942A518>
<__main__.Singleton object at 0x00000177495FA160>
<__main__.Singleton object at 0x00000177495F1EF0>
<__main__.Singleton object at 0x00000177495E5940>
<__main__.Singleton object at 0x00000177495AC0F0>
<__main__.Singleton object at 0x00000177495E58D0>
<__main__.Singleton object at 0x00000177495A6FD0>
<__main__.Singleton object at 0x00000177495F1D68>
<__main__.Singleton object at 0x00000177496060B8>
<__main__.Singleton object at 0x0000017749606240> 

当初咱们是不是就发现了这个实例对象变为了不同的 ip 了?这个也就是咱们方才所说道的这个起因。那么咱们怎么去解决这问题呢?在这里咱们能够通过加上线程锁达到成果。咱们加了线程锁之后,给这个_instance 加上这个锁,让每一个县城用完了之后才开释它,接着下一个线程能力拿到它持续运行程序,就不会再造成同时拿到这个属性值而间接创立了不同的实例的状况。

import threading
import time

class Singleton(object):
    _instance_lock = threading.Lock()
    def __init__(self, *args, **kwargs):
        time.sleep(1)

    @classmethod
    def get_instance(cls, *args, **kwargs):
        if not hasattr(Singleton, '_instance'):
            with Singleton._instance_lock:
                if not hasattr(Singleton, '_instance'):
                    Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance

def task(arg):
    obj = Singleton.get_instance(arg)
    print(obj)

for i in range(10):
    t = threading.Thread(target=task, args=[i,])
    t.start()
执行后果:<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550> 

4. 应用__new__办法实现单例模式

大家应该都晓得咱们的实例对象就是通过__new__办法创立进去的,如果咱们重写类的__new__办法,咱们是不是能够间接把这个能够创立多个实例的状况改写为只能创立一个实例对象呢?答案是必定的, 咱们能够在重写__new__办法的时候,增加判断如果存在了实例对象,那么间接返回曾经创立了的对象即可,这样就能够达到咱们的这个成果了。

import threading

class Singleton(object):
    _instance_lock = threading.Lock()

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

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

            return Singleton._instance

def task(arg):
    obj = Singleton()
    print(obj)

for i in range(10):
    t = threading.Thread(target=task, args=[i,])
    t.start()
运行后果:<__main__.Singleton object at 0x00000111C88B9588>
None
None
None
None
None
None
None
None
None 

5. 应用元类 metaclass 实现单例模式

Python 的元类是管制类的创立的类,既然如此,那么咱们是不是能够再在创立类的时候通过元类来创立咱们的类,使得它只能生成一个实例对象呢?

import threading

class SingletonType(type):
    _instance_lock = threading.Lock()
    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            with SingletonType._instance_lock:
                if not hasattr(cls, "_instance"):
                    cls._instance = super(SingletonType,cls).__call__(*args, **kwargs)
        return cls._instance

class Foo(metaclass=SingletonType):
    def __init__(self,name):
        self.name = name

obj1 = Foo('name')
obj2 = Foo('name')
print(id(obj1))
print(id(obj2))
执行后果:<__main__.Foo object at 0x000001E97FC14400>
<__main__.Foo object at 0x000001E97FC14400> 

好了,那么这里就是给大家介绍的几种创立单例模式的办法,总的来说,不论有没有用,面试的时候,咱们装 x 还是有作用的,你 get 到了吗?

退出移动版