单例模式是某个类在整个系统中只存在一个实例的一种设计模式。
使用单例模式的好处:
- 单例模式不仅可以减少内存资源占用,而且因为只初始化一次,还可以加快运行性能。例如当程序通过一个类来读取配置信息,而程序多个地方需要使用配置信息,这时整个程序运行过程中只需一个实例对象即可,可减少占用内存资源,同时还可以保证程序在多处地方获取的配置信息一致。
- 使用单例模式可进行同步控制,计数器同步、程序多处读取配置信息这些情景下若只存在一个实例,即可保证一致性。
Python实现单例模式有4种方式:
- 通过模块调用
- 使用__new__方法
- 使用装饰器
- 使用元类(metaclass)
一、通过模块调用
在python3中,首次导入模块文件时,会在程序目录下的__pycache__目录中生成pyc文件,再导入时,将直接加载pyc文件。因此,只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。
- 定义singleton_demo.py模块
class singleton_cal: def foo(self) pass export_singleton = singleton_cal()
- 使用模块
from singleton_demo import export_singleton a = export_singleton b = export_singleton print(id(a) == id(b))
二、使用__new__方法
- __new__:创建实例对象时调用的构造方法
- __init__ :实例初始化方法,用于设置实例的相关属性
当实例化一个对象时,先调用__new__方法(未定义时调用object.__new__)实例化对象,然后调用__init__方法进行对象初始化。
所以,可以声明一个私有类变量__instance。当__instance不为None时,表示系统中已有实例,直接返回该实例;若__instance为None时,表示系统中还没有该类的实例,则创建新实例并返回。
class Singleton(object): __instance = None
def __new__(cls, *args, **kwargs): if not cls.__instance: cls.__instance = super().__new__(cls, *args, **kwargs) return cls.__instance a = Singleton() b = Singleton() print(id(a) == id(b))
三、使用装饰器
from functools import wraps def singleton(cls): instances = {}
@wraps(cls) def get_instance(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return get_instance @singleton class Singleton(object): def foo(self): pass a = Singleton() b = Singleton() print(id(a) == id(b))
只有第一次调用Singleton类时,装饰器才会从instances={}开始执行,以后再调用该类时,都只执行get_instance函数,这是装饰器的特性。
利用装饰器的这个特性,可以实现单例模式。复用装饰器,可以使多个类实现单例模式。
四、使用元类
元类创建了所有的类型对象(包括object对象),系统默认的元类是type。
执行顺序:先定义metaclass,然后在类定义时,通过metaclass创建类,最后通过定义好的类创建实例。
所以,metaclass允许你创建类或者修改类。换句话说,可以把类看成是metaclass创建出来的“实例”。
元类中定义的__new__方法,在以该类为元类的类定义时自动调用。例如:类A以类B为元类,当定义类A时,类B的__new__方法将会被自动调用。
元类中定义的__call__方法,在以该类为元类的类创建实例时自动调用。例如:类A以类B为元类,当类A创建实例时,类B的__call__方法将会被自动调用。
1、元类的使用
自定义元类时,通常继承自type。
class MetaClass(type): def __init__(cls, *args, **kwargs):
# cls 代指以该类为元类的类 Foo super(MetaClass, cls).__init__(*args, **kwargs)
def __new__(mcs, *args, **kwargs):
# mcs 代指元类自身
print("MetaClass.__new__: ", mcs)
return super().__new__(mcs, *args, **kwargs) def __call__(cls, *args, **kwargs):
# cls 代指以该类为元类的类 Foo print("CLS: ", cls) obj = cls.__new__(cls, *args, **kwargs) cls.__init__(obj, *args, **kwargs) return obj class Foo(metaclass=MetaClass):
# 定义类Foo时,将调用元类的__new__方法和__init__方法。就跟一般普通类实例化时调用__new__方法和__init__方法一样。 def __init__(self, name): self.name = name
# Foo 实例化时会调用元类的__call__方法。 a = Foo("ABC")
2、元类实现单例模式
声明一个私有变量__instance保存类实例。__instance为None时,调用type的__call__方法为类创建实例。
class SingletonMeta(type): __instance = None def __call__(cls, *args, **kwargs): if not cls.__instance: cls.__instance = type.__call__(cls, *args, **kwargs) return cls.__instance class MyClass(metaclass=SingletonMeta): def foo(self): pass a = MyClass() b = MyClass() print(id(a) == id(b))
五、使用类
class Singleton(object): def __init__(self): pass @classmethod def instance(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"): Singleton._instance = Singleton(*args, **kwargs) return Singleton._instance
线程安全
通过以上方法定义的单例模式,无法支持多线程。解决这个问题的办法是:加锁!未加锁部分并发执行,加锁部分串行执行。
import threading class Singleton(object): _instance_lock = threading.Lock() @classmethod
def instance(cls, *args, **kwargs):
if not hasattr(Singleton, "_instance"):
with Singleton._instance_lock:
Singleton._instance = Singleton(*args, **kwargs)
return Singleton._instance
def task(arg): obj = Singleton.instance() print("Task {}".format(arg), id(obj)) for i in range(10): t = threading.Thread(target=task, args=[i,]) t.start()
使用类实现的单例模式,在使用时必须通过Singleton.instance()进行实例化。如果使用Singleton()进行实例化得到的不是单例。