zoukankan      html  css  js  c++  java
  • python中实现单例模式

    单例模式的目的是一个类有且只有一个实例对象存在,比如在复用类的过程中,可能重复创建多个实例,导致严重浪费内存,此时就适合使用单例模式。

    前段时间需要用到单例模式,就称着机会在网上找了找,有包含了__new__方法在内的5种单例模式,就顺便记录于此。


    基于模块导入机制的实现

    第一次执行程序时编译为.pyc文件,而第二次执行时会直接执行.pyc。基于此机制,可以通过把类和所创建的实例单独写在某模块内,在使用时直接从这么模块中导入即可,这个导入的实例对象即唯一对象。

    # test.py文件
    class Test(object):
        pass
    # 创建实例对象
    t = Test()
    
    # 在其他文件中
    from test import t

    基于装饰器的实现

    def singleton(cls):
        instance = None
        
        def wrap(*argrs, **kwargs):
            nonlocal instance
            if instance is None:
                instance = cls(*args, **kwargs)
            return instance
    
        return wrap
    
    @singleton
    class Test(object):
    
        def __init__(self, *args, **kwargs):
            pass
    
    t1 = Test()
    t2 = Test()
    print(t1 is t2)  # 输出True

    基于类(类方法)的实现(这个看别人的博客学来的)

    class Test(object):
    
        def __init__(self):
            pass

         @classmethod def instance(cls, *args, **kwargs):
         # 每次调用此类方法即可创建实例对象
    if not hasattr(Test, "_instance"): Test._instance = Test(*args, **kwargs) return Test._instance

    上述的实现方法在使用多线程时会出现问题,即这种上述实现单例模式不支持多线程

    import threading
    import time
    
    class Test(object):
    
        def __init__(self):
            time.sleep(2)
    
        @classmethod
        def instance(cls, *args, **kwargs):
            if not hasattr(Test, "_instance"):
                Test._instance = Test(*args, **kwargs)
            return Test._instance
    
    def task(args):
        obj = Test.instance()
        print(obj)
    
    for i in range(10):
        t = threading.Thread(target=task,args=[i,])
        t.start()

    注意,如果上述不用sleep()暂停,执行速度过快时,仍可能存在相同地址

    解决方法:加锁

    import threading
    import time
    
    class Test(object):
        _instance_lock = threading.Lock()
    
        def __init__(self):
            time.sleep(2)
    
        @classmethod
        def instance(cls, *args, **kwargs):
            with Test._instance_lock:
                if not hasattr(Test, "_instance"):
                    Test._instance = Test(*args, **kwargs)
            return Test._instance
    
    def task(args):
        obj = Test.instance()
        print(obj)
    
    for i in range(10):
        t = threading.Thread(target=task,args=[i,])
        t.start()
    
    # 检查长时间后是否为单例
    time.sleep(20)
    obj = Test.instance()
    print(obj)

    上述方案长时间后虽然仍为单例模式,但是仍处于加锁的状态,可通过将锁的位置放在判断实例是否存在之后,如果不存在需要重新创建时再加锁

    @classmethod
    def instance(cls, *args, **kwargs):       
        if not hasattr(Test, "_instance"):
            with Test._instance_lock:
                Test._instance = Test(*args, **kwargs)
        return Test._instance          

    上述这种方法的问题在于创建实例对象时是通过调用Test.instance()创建的,而不是常规那样创建

    基于__new__方法的实现(最常见)

    在创建一个实例对象时,先调用__new__方法,再调用__init__方法

    import threading
    import time
    class Test(object): def __init__(self): time.sleep(1) def __new__(cls, *args, **kwargs): if not hasattr(Test, "_instance"): Test._instance = object.__new__(cls) # 相当于继承 return Test._instance obj1 = Test() obj2 = Test() print(obj1, obj2) def task(arg): obj = Test() print(obj) for i in range(10): t = threading.Thread(target=task,args=[i,]) t.start()

    也可像之前装饰器一样用一个类属性来判断是否已有创建好的实例对象,如果没有则在__new__中创建,注意必须返回实例对象

    这种方法不受线程影响

    基于元类方法的实现

    python中是类由type创建,在创建时类时会先调用type的__init__方法,然后创建实例对象时会调用type的__call__方法

    顺序如下:type的__init__方法(创建类) -> type的__call__方法(类创建实例对象) -> 类的__new__方法(类创建实例对象) -> 类的__init__方法(类初始化实例对象)

    元类的使用方式就是写一个继承了type的类,然后在我们所需的类中指定元类(通过metaclass参数),而继承了type的类则可重新定义类的构造函数,单例模式则可在__call__方法中定义

    class SingletonType(type):
    def __call__(cls, *args, **kwargs): if not hasattr(cls, "_instance"): cls._instance = super(SingletonType,cls).__call__(*args, **kwargs) return cls._instance class Test(metaclass=SingletonType): def __init__(self): pass obj1 = Test() obj2 = Test() print(obj1,obj2)

    注意:在元类中__call__的参数cls指所创建的类

  • 相关阅读:
    SqlServer2008启动不了的问题
    .exe 不包含适合入口点的静态“Main”方法
    两种常用的序列化
    异或运算^
    SqlServer数据库实现C#中的split功能
    遍历电脑下面所有文件--递归
    ExecuteNonQuery引发了System.ArgumentException类型异常
    JQuery属性过滤(转)
    SQL SERVER 执行远端数据库的SQL命令
    马云:做一个静静的观察者 能学到更多
  • 原文地址:https://www.cnblogs.com/eat-w/p/12071005.html
Copyright © 2011-2022 走看看