本文作为Python设计模式的第一篇文章,先来介绍比较简单的单例模型。单例模式提供一种全局的访问点,确保类有且仅有一个实例对象。由于在Python语言中对象的概念过于广泛,类创建的对象记为类的实例,用instance而不是object。
单例模式应用较多,例如日志的记录、数据库的操作等通常都需要使用单个实例。最近项目中使用Celery模块,celery处理异步任务时,子线程会fork主线程,会导致每个每个线程都会创建一个实例,如果使用单例模型,则会减少实例的创建,节省系统资源。
介绍
单例模式总结起来:
1. 确保类有且只有一个实例被创建;
2. 为实例提供一个访问点,以使程序可以全局访问该实例;
3. 控制共享资源的并行访问
实现单例模式的一个简单方法是,使构造函数私有化,并创建一个静态方法来完成对象的初始化。这样对象将在第一次调用时创建,此后,这个类将返回同一个对象。
class Singleton(object):
def __new__(cls, *args, **kwargs):
print("call __new__ method.")
if not hasattr(cls, 'instance'):
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instance
def __init__(self):
print("call __init__ method.")
s1 = Singleton()
s2 = Singleton()
print(f"s1 objected f{s1}")
print(f"s2 objected f{s2}")
输出:
call __new__ method.
call __init__ method.
call __new__ method.
call __init__ method.
s1 objected f<__main__.Singleton object at 0x7fcdc69ff590>
s2 objected f<__main__.Singleton object at 0x7fcdc69ff590>
可以看到两次创建的实例在内存的地址是一样的,说明两次创建的是同一个实例。
懒加载方式创建单例
上面Singleton
直接创建实例,不管这个实例是否被使用,如果这个实例没有被使用也造成了资源的浪费,当然垃圾回收机制也会在后期把不使用的对象回收。不过我们还能借助懒加载的方式,实例化时并没有正常创建实例,而是在调用的时候才创建。要实现懒加载只需要把实例化的过程放在类方法就可以。
# lazy load instance
class Single(object):
__instance = None
def __init__(self):
if not Single.__instance:
print("Single __init__ method called..")
else:
print("Single Instance already created:", self.get_instance())
@classmethod
def get_instance(cls):
if not cls.__instance:
cls.__instance = Single()
return cls.__instance
s1 = Single()
print("object created", Single.get_instance())
s2 = Single()
输出
Single __init__ method called..
Single __init__ method called..
object created <__main__.Single object at 0x7f85112f7e10>
Single Instance already created: <__main__.Single object at 0x7f85112f7e10>
在第一次实例化时__init__
方法被调用,真正实例化的方法是get_instance
。然后调用get_instance
方法,创建实例,私有属性__instance
缓存了实例。第二次实例化时直接调用get_instance
方法,从__instance
属性拿到缓存的实例。
元类的方式创建单例
元类是一种控制子类实例化过程的类,是“类的类”,类在实例化过程中的一些细节可以由元类来控制。在创建单例的过程中,我们想把创建出来的实例缓存起来,而不是重新创建实例,可以把这个过程放在元类中实现。
class MetaSingle(type):
_instance = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instance:
cls._instance[cls] = \
super(MetaSingle, cls).__call__(*args, **kwargs)
return cls._instance[cls]
class Logger(metaclass=MetaSingle):
pass
logger1 = Logger()
logger2 = Logger()
print(logger1)
print(logger2)
# <__main__.Logger object at 0x7fb3b3af4f10>
# <__main__.Logger object at 0x7fb3b3af4f10>
总结
虽然单例模式在许多情况下效果很好,但这种模式仍然存在一些缺陷。由于单例具有全局访问权限,因此可能会出现以下问题
- 全局变量可能在某处已经被误改,但是开发人员仍然认为它们没有发生变化,而该变量还在应用程序的其他位置被使用。
- 可能会对同一对象创建多个引用。由于单例只创建一个对象,因此这种情况下会对同一个对象创建多个引用。
- 所有依赖于全局变量的类都会由于一个类的改变而紧密耦合为全局数据,从而可能在无意中影响另一个类。