Python实现Singleton模式的几种方式
使用python实现设计模式中的单例模式。单例模式是一种比较常用的设计模式,其实现和使用场景判定都是相对容易的。本文将简要介绍一下python中实现单例模式的几种常见方式和原理。一方面可以加深对python的理解,另一方面可以更加深入的了解该模式,以便实际工作中能更加灵活的使用单例设计模式。
本文将介绍常见的实现单例模式的几种方式,这里暂不考虑多线程的情况。
为了准备该篇博文,之前写了几篇相关的文章依次完整的介绍了相关的概念,下面会在需要的时候给出链接。
装饰器作为python实现单例模式的一种常用方法,先简单了解一下其概念。
1.装饰器
装饰器(Decorator)可以用作对函数以及类进行二次包裹或者封装,使用方式@wrapper。
- def f(...):
- ...
- f = staticmethod(f)
- @staticmethod
- def f(...):
- ...
上面这两种方式对函数的定义在语法上是等价的。当然对于类也有同样的用法,类可以作为装饰器也可以作为被装饰对象。唯一的区别就是经过包裹的类可能不在是一个类,而是一个类的对象或者一个函数,这取决于装饰器返回的值。
经过Decorator装饰的类或者函数本质上已经不再是原来的类或者函数了。但是,实际上在包裹之后得到的新对象仍然拥有被包裹对象的特性(这句是不是废话:-))。
在python中我们经常只需要实现一个装饰器,然后使用该装饰器作用于只能有唯一一个实例的类。这样只需要实现一个这样的装饰器,便可以作用于任何一个想要唯一实例的类。
2.闭包方式
闭包的应用很多,单例模式则是其应用之一。先看代码:
- def singleton(cls):
- instances = {}
- def getinstance(*args, **kwargs):
- if cls not in instances:
- instances[cls] = cls(*args, **kwargs)
- return instances[cls]
- return getinstance
- @singleton
- class my_cls(object):
- pass
这个实现单例模式的方式将原来类的定义隐藏在闭包函数中,通过闭包函数及其中引用的自由变量来控制类对象的生成。由于唯一的实例存放在自由变量中,而且自由变量是无法直接在脚本层进行访问的。这种方式非常隐蔽的保护实例不被修改,因此很适合用于单例模式。
这种方式简单明了,很容易实现。但是如果不了解闭包实现过程和变量的绑定等概念可能会不明白其实现的过程。建议参考一下我的另一篇博文:理解python闭包概念。
这里一个很有趣的地方是为什么要使用instances = {}这样一个变量?可不可以不用字典,使用instance = None?如果singleton作为装饰器被多个不同的类使用,那么instance中会存在几个不同的实例么?
有时间可以思考一下这几个问题,答案也可以在我写的闭包相关的博文中找到。
3.元类方式
所谓单例模式,即我们需要控制类实例的生成过程,并且保证全局只可能存在一个唯一的实例。既然需要在创建类的对象过程中做些什么,应该很容易想到元类。参照介绍元类的文章:python metaclass深入分析。
- class Singleton(type):
- def __init__(cls, name, bases, dic):
- super(Singleton, cls).__init__(name, bases, dic)
- cls._instance = None
- def __call__(cls, *args, **kwargs):
- if cls._instance is None:
- cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
- # cls._instance = cls(*args, **kwargs) # Error! Lead to call this function recursively
- return cls._instance
- class my_cls(object):
- __metaclass__ = Singleton
这个例子中我们使用元类Singleton替代默认使用type方式创建类my_cls。可以将类my_cls看做是元类Singleton的一个对象,当我们使用my_cls(...)的方式创建类my_cls的对象时,实际上是在调用元类Singleton的对象my_cls。
对象可以以函数的方式被调用,那么要求类中定义__call__函数。不过此处被调用的是类,因此我们在元类中定义函数__call__来控制类my_cls对象创建的唯一性。
这种方式的弊端之一就是类唯一的对象被存放在类的一个静态数据成员中,外部可以通过class_name._instance的方式修改甚至删除这个实例(该例中my_cls._instance = None完全合法)。
4.类作为装饰器之__call__方式
不仅函数可以作为装饰器,类也可以作为装饰器。
下面简单的介绍一下使用类作为装饰器实现单例模式的另一种方式。
- class Singleton(object):
- _INSTANCE = {}
- def __init__(self, cls):
- self.cls = cls
- def __call__(self, *args, **kwargs):
- instance = self._INSTANCE.get(self.cls, None)
- if not instance:
- instance = self.cls(*args, **kwargs)
- self._INSTANCE[self.cls] = instance
- return instance
- def __getattr__(self, key):
- return getattr(self.cls, key, None)
- @Singleton
- class my_cls(object):
- pass
函数作为装饰器返回的是一个函数,函数被调用过程中实际上是间接地调用其内部包裹的被装饰的对象。
类作为装饰器要想达到相同的效果只需要将类的对象返回,并且其对象是可以调用的。这是上面这个例子表达的一个核心思想。
这种方式写法很多,也很灵活,其思想基本上就是对被包裹对象的调用实际上调用的是类对象的__call__函数,该函数实际上是对被装饰对象的一次封装。
5.类本身实现方式
上面的例子中我们都是使用的装饰器或者元类的方式间接的通过控制类对象生成的方式来保证对象的唯一性,那么有没有办法直接在类中通过某种方式保证类对象的唯一性?
答案是肯定的。参考我之前写的一篇介绍元类的文章,可知生成对象前会调用函数__new__,如果__new__函数返回被创建的对象,那么会自动调用类中定义的__init__函数进行对象的初始化操作。
相信读了上面这句话,应该知道我们接下来要干什么了?没错,我们的目标就是__new__。
- class MSC(object):
- _INSTANCE = None
- def __new__(cls, *args, **kwargs):
- if not cls._INSTANCE:
- cls._INSTANCE = super(MSC, cls).__new__(cls, *args, **kwargs)
- # cls._INSTANCE.args = args
- # cls._INSTANCE.kwargs = kwargs
- return cls._INSTANCE
- def __init__(self, *args, **kwargs):
- pass
在这个例子中,我们完全可以理解为什么只会有一个类的对象会被创建。这种方式的定义决定了类本身只能被创建一个对象。
但是这里有一点需要注意,那就是不管创建多少MSC的对象,至始至终只会有一个对象,但是如果每次创建的时候传入的参数都不同,也就是__init__函数中参数不同,会导致同一个对象被多次初始化。
这种方式的弊端显然很明显,那就是该方法只能作用于单个类的定义。不能像上面的装饰器和元类,一次实现,可以到处使用。
那能不能将这个控制类生成过程的结构单独抽象出来呢?而且有没有什么方法能防止同一个对象多次被__init__初始化。下面我们看一种能被不同的类使用的更加抽象的结构。
6.替换__new__方式
我们定义的类作为一个对象,通过替换其部分属性可以达到控制类对象生成的目的。
- def Singleton(cls):
- _instance = {}
- cls._origin_new = cls.__new__
- cls._origin_init = cls.__init__
- @functools.wraps(cls.__new__)
- def _singleton_new(cls, *args, **kwargs):
- if cls not in _instance:
- sin_instance = cls._origin_new(cls, *args, **kwargs)
- sin_instance._origin_init(*args, **kwargs)
- _instance[cls] = sin_instance
- return _instance[cls]
- # As a special case,__new__ is a staticmethod, need convert function to staticmethod by self
- cls.__new__ = staticmethod(_singleton_new)
- # setattr(cls, '__new__', staticmethod(_singleton_new))
- cls.__init__ = lambda self, *args, **kwargs: None
- # setattr(cls, '__init__', lambda self, *args, **kwargs: None)
- return cls
- @Singleton
- class my_cls(object):
- pass
上面我们通过替换类的__new__函数和__init__函数的方式,保证被Singleton装饰的类只有一个对象会被原来的__new__和__init__生成和初始化。
这里必须要替换类的__init__函数,而且该函数应该什么都不做。原因在于替换之后的__new__返回唯一的对象后,会自动调用现在的__init__函数。
原来的__init__函数已经在创建唯一一个对象时被调用过。而且只能被调用一次。
这里返回的并不是闭包结构,只是使用装饰器修改了类的部分属性,返回的仍是传入的类。但是类的__new__函数引用了Singleton中的local variable _instance。
- my_cls.__new__.func_closure[0].cell_contents
- ==
- {<class '__main__.my_cls'>: <__main__.my_cls object at 0x02954810>}
- ==
- _instance
Cell 对象my_cls.__new__.func_closure[0]中存放的便是类my_cls唯一的实例。
当然我们可以将my_cls唯一的对象作为类的一个静态数据成员放入cls.__dict__中来替代_instance = {},但是显然闭包结构更适合。
7.注意事项
文中借助python语言的类创建对象过程的相关原理,介绍了几种不同的单例模式实现方式。
为了保留被装饰对象的一些属性,可以使用@functools.wraps的方式对返回的闭包进行装饰。
平时建议使用前两种实现方式,也就是闭包方式和元类方式。其他情况多少有点玩弄python语法技巧的一些嫌疑,当然了,作为学习python来说还是比较有意义的。
建议多关注语言特性的应用以及如何的解决实际的问题,不要沉迷于语言实现的一些细枝末节。本末倒置总会有些得不偿失嘛。尤其是python作为一种非常实用的语言。
本文介绍中如果有什么不当之处欢迎指正,如果有其他的更好的实现方式也请不吝赐教。
Python实现Singleton模式的几种方式的更多相关文章
- JAVA中实现单例(Singleton)模式的八种方式
单例模式 单例模式,是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例.即一个类只有一个对象实例. 基本的实现思路 单 ...
- 单例Singleton模式的两种实现方法
在设计模式中,有一种叫Singleton模式的,用它可以实现一次只运行一个实例.就是说在程序运行期间,某个类只能有一个实例在运行.这种模式用途比较广泛,会经常用到,下面是Singleton模式的两种实 ...
- Python实现微信支付(三种方式)
Python实现微信支付(三种方式) 关注公众号"轻松学编程"了解更多. 如果需要python SDk源码,可以加我微信[1257309054] 在文末有二维码. 一.准备环境 1 ...
- 内核知识第12讲,SSDT表.以用户模式到系统模式的两种方式.
内核知识第12讲,SSDT表.以用户模式到系统模式的两种方式. 一丶IDT解析. 我们知道.IDT表中存放着各种中断信息.比如当我们调用int 3的时候,则会调用IDT表中的第三项来进行调用. 而函数 ...
- 【转】python之配置日志的几种方式
[转]python之配置日志的几种方式 作为开发者,我们可以通过以下3种方式来配置logging: 1)使用Python代码显式的创建loggers, handlers和formatters并分别调用 ...
- Python调用API接口的几种方式 数据库 脚本
Python调用API接口的几种方式 2018-01-08 gaoeb97nd... 转自 one_day_day... 修改 微信分享: 相信做过自动化运维的同学都用过API接口来完成某些动作.AP ...
- python获取公网ip的几种方式
python获取公网ip的几种方式 转 https://blog.csdn.net/conquerwave/article/details/77666226 from urllib2 import u ...
- Python调用API接口的几种方式
Python调用API接口的几种方式 相信做过自动化运维的同学都用过API接口来完成某些动作.API是一套成熟系统所必需的接口,可以被其他系统或脚本来调用,这也是自动化运维的必修课. 本文主要介绍py ...
- Python爬虫解析网页的4种方式 值得收藏
用Python写爬虫工具在现在是一种司空见惯的事情,每个人都希望能够写一段程序去互联网上扒一点资料下来,用于数据分析或者干点别的事情. 我们知道,爬虫的原理无非是把目标网址的内容下载下来存储到内存 ...
随机推荐
- 集成支付宝SDK流程
5.2 SDK集成流程 5.2.1 iOS 解压接口压缩文件(文件名是 WS_MOBILE_PAY_SDK_BASE.zip),找到iOS的压缩文件(文件名是支付宝移动支付SDK 标准版(iOS).z ...
- Django中Middleware中间件
Django中Middleware中间件 1 Middleware中间件概述 django中间middleware实质就是一个类,django会根据自己的规则在合适的时机执行中间件相应的方法.实际上当 ...
- 第十四章——循环神经网络(Recurrent Neural Networks)(第一部分)
由于本章过长,分为两个部分,这是第一部分. 这几年提到RNN,一般指Recurrent Neural Networks,至于翻译成循环神经网络还是递归神经网络都可以.wiki上面把Recurrent ...
- 【CTF 攻略】CTF比赛中关于zip的总结
[CTF 攻略]CTF比赛中关于zip的总结 分享到: --> 本文首发于安全客,建议到原地址阅读,地址:http://bobao.360.cn/ctf/detail/203.html 前言 ...
- 机器学习web服务化实战:一次吐血的服务化之路
背景 在公司内部,我负责帮助研究院的小伙伴搭建机器学习web服务,研究院的小伙伴提供一个机器学习本地接口,我负责提供一个对外服务的HTTP接口. 说起人工智能和机器学习,python是最擅长的,其以开 ...
- 轻量级原生 ajax 函数,支持 get/array post/array post/json
原生js封装 function ajaxRequest(type, url, data, callback, failCallBack, header, dataType) { var url_enc ...
- 服务端预渲染之Nuxt(爬坑篇)
Nuxt是解决SEO的比较常用的解决方案,随着Nuxt也有很多坑,每当突破一个小技术点的时候,都有很大的成就感,在这段时间里着实让我痛并快乐着.在这里根据个人学习情况,所踩过的坑做了一个汇总和总结. ...
- 小步快跑的公司可以最简化操作直接通过log4net将日志写入ElasticSearch
很多小步快跑的公司,开发人员多则3-4个,面对巨大业务压力,日连夜的赶着上线,快速试错,自然就没时间搭建一些基础设施,比如说logCenter,但初期 项目不稳定,bug又多,每次都跑到生产去找日志 ...
- kubernetes实践之一:kubernetes二进制包安装
kubernetes二进制部署 1.环境规划 软件 版本 Linux操作系统 CentOS Linux release 7.6.1810 (Core) Kubernetes 1.9 Docker 18 ...
- Python函数的定义、参数传入与函数的调用
作为计算机代码的一种抽象方式,函数在Python中扮演了极为重要的角色.今天给大家介绍Python函数的定义.参数的传入以及调用方式.其中函数参数的传入方式为本节重点内容.Python函数的参数形式包 ...