问题:
现代化的巧克力工厂具有计算机控制的巧克力锅炉。锅炉作的事情就是把巧克力和牛奶融在一块儿,而后送到下一个阶段,以制成巧克力棒。下边是一个巧克力公司锅炉控制器的代码,仔细观察一下,这段代码有什么问题?html
class ChocolateBoiler(object): def __init__(self): self.empty = True self.boiled = False def fill(self): # 向锅炉填充巧克力和牛奶混合物 # 在锅炉内填充原料时,锅炉必须是空的。 # 一旦填入原料,就要把empty 和 boiled 标志设置好 if self.empty: self.empty = False self.boiled = False def drain(self): # 排出煮沸的巧克力和牛奶 # 锅炉排出时,必须是满的且煮沸的。 # 排出完毕empty 设置为 true if not self.empty and self.boiled: self.empty = True def boil(self): # 将颅内物煮沸 # 煮混合物时,锅炉内必须是满的且没有煮沸过 # 一旦煮沸,就把 boiled 设置为 true if not self.empty and not self.boiled: self.boiled = True
从代码能够看出,他们加入了多种判断,以防止很差的事情发生。若是同时存在两个ChocolateBoiler
实例,那这么多判断岂不是失去做用了。那咱们改如何实现这个需求呢?这个问题的核心是,咱们要先判断实例是否是已经存在,若是存在就再也不建立。python
_chocolate_boiler_instance = None # 声明实例 def chocolate_boiler(): global _chocolate_boiler_instance # 使用全局变量 if _chocolate_boiler_instance is not None: # 判断是否存在,若是存在,直接返回 return _chocolate_boiler_instance else: # 若是不存在,建立一个新的 _chocolate_boiler_instance = ChocolateBoiler() return _chocolate_boiler_instance
如今咱们须要获取 ChocolateBoiler
实例的时候只须要调用 chocolate_boiler 方法获取实例便可保证同时只有一个 ChocolateBoiler
实例。git
这种保证 ChocolateBoiler
类只有一个实例,并提供一个全局访问点的模式,就是单例模式
。github
单例模式:
确保一个类只有一个实例,并提供一个全局访问点。api
python 实现单例模式有多种方案:函数
《python cookbook》提供了很是易用的 Singleton
类,只要继承它,就会成为单例。ui
# python 3 代码实现 class Singleton(type): def __init__(self, *args, **kwargs): self.__instance = None super().__init__(*args, **kwargs) def __call__(self, *args, **kwargs): if self.__instance is None: # 若是 __instance 不存在,建立新的实例 self.__instance = super().__call__(*args, **kwargs) return self.__instance else: # 若是存在,直接返回 return self.__instance class Spam(metaclass=Singleton): def __init__(self): print('Creating Spam') a = Spam() b = Spam() print(a is b) # 这里输出为 True
元类(metaclass)能够控制类的建立过程,它主要作三件事:spa
例子中咱们构造了一个Singleton元类,并使用__call__方法使其可以模拟函数的行为。构造类 Spam 时,将其元类设为Singleton,那么建立类对象 Spam 时,行为发生以下:设计
Spam = Singleton(name,bases,class_dict),Spam 其实为Singleton类的一个实例。code
建立 Spam 的实例时,Spam()=Singleton(name,bases,class_dict)()=Singleton(name,bases,class_dict).__call__(),这样就将 Spam 的全部实例都指向了 Spam 的属性 __instance上。
咱们能够使用 new 来控制实例的建立过程,代码以下:
class Singleton(object): __instance = None def __new__(cls, *args, **kw): if not cls.__instance: cls.__instance = super().__new__(cls, *args, **kw) return cls.__instance class Foo(Singleton): a = 1 one = Foo() two = Foo() assert one == two assert one is two assert id(one) == id(two)
经过 new 方法,将类的实例在建立的时候绑定到类属性 __instance 上。若是cls.__instance 为None,说明类还未实例化,实例化并将实例绑定到cls.__instance 之后每次实例化的时候都返回第一次实例化建立的实例。注意从Singleton派生子类的时候,不要重载__new__。
import functools def singleton(cls): ''' Use class as singleton. ''' # 首先将 __new__ 方法赋值给 __new_original__ cls.__new_original__ = cls.__new__ @functools.wraps(cls.__new__) def singleton_new(cls, *args, **kw): # 尝试从 __dict__ 取 __it__ it = cls.__dict__.get('__it__') if it is not None: # 若是有值,说明实例已经建立,返回实例 return it # 若是实例不存在,使用 __new_original__ 建立实例,并将实例赋值给 __it__ cls.__it__ = it = cls.__new_original__(cls, *args, **kw) it.__init_original__(*args, **kw) return it # class 将原有__new__ 方法用 singleton_new 替换 cls.__new__ = singleton_new cls.__init_original__ = cls.__init__ cls.__init__ = object.__init__ return cls # # 使用示例 # @singleton class Foo: def __new__(cls): cls.x = 10 return object.__new__(cls) def __init__(self): assert self.x == 10 self.x = 15 assert Foo().x == 15 Foo().x = 20 assert Foo().x == 20
这种方法的内部实现和使用 __new__
相似:
将名字singleton绑定到实例上,singleton就是它本身类的惟一对象了。
class singleton(object): pass singleton = singleton()
https://github.com/gusibi/Metis/blob/master/apis/v1/schemas.py#L107 使用的就是这种方式,用来获取全局的 request
Python 的模块就是自然的单例模式,由于模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。所以,咱们只需把相关的函数和数据定义在一个模块中,就能够得到一个单例对象了。
最后,感谢女友支持。
欢迎关注(April_Louisa) | 请我喝芬达 |
---|---|
![]() |
![]() |