python单例模式的多种实现方法


Posted in Python onJuly 26, 2019

前言

单例模式(Singleton Pattern),是一种软件设计模式,是类只能实例化一个对象,

目的是便于外界的访问,节约系统资源,如果希望系统中 只有一个对象可以访问,就用单例模式,

显然单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

在 Python 中,我们可以用多种方法来实现单例模式:

  • 使用模块
  • 使用 __new__
  • 使用装饰器(decorator)
  • 使用元类(metaclass)

概念

简单说,单例模式(也叫单件模式)的作用就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个(当然也可以不存在)

例子:

一台计算机上可以连好几个打印机,但是这个计算机上的打印程序只能有一个,这里就可以通过单例模式来避免两个打印作业同时输出到打印机中,即在整个的打印过程中我只有一个打印程序的实例。

super(B, self).__init__()是这样理解的:super(B, self)首先找到B的父类(就是类A),然后把类B的对象self转换为类A的对象(通过某种方式,一直没有考究是什么方式,惭愧),然后“被转换”的类A对象调用自己的__init__函数。考虑到super中只有指明子类的机制,因此,在多继承的类定义中,通常我们保留使用类似代码段1的方法。

1. super并不是一个函数,是一个类名,形如super(B, self)事实上调用了super类的初始化函数,
产生了一个super对象;

2. super类的初始化函数并没有做什么特殊的操作,只是简单记录了类类型和具体实例;

3. super(B, self).func的调用并不是用于调用当前类的父类的func函数;

4. Python的多继承类是通过mro的方式来保证各个父类的函数被逐一调用,而且保证每个父类函数
只调用一次(如果每个类都使用super);

5. 混用super类和非绑定的函数是一个危险行为,这可能导致应该调用的父类函数没有调用或者一
个父类函数被调用多次。

__new__: 对象的创建,是一个静态方法,第一个参数是cls。(想想也是,不可能是self,对象还没创建,哪来的self)

__init__ : 对象的初始化, 是一个实例方法,第一个参数是self。

__new__方法在类定义中不是必须写的,如果没定义,默认会调用object.__new__去创建一个对象。如果定义了,就是override,可以custom创建对象的行为。

聪明的读者可能想到,既然__new__可以custom对象的创建,那我在这里做一下手脚,每次创建对象都返回同一个,那不就是单例模式了吗?没错,就是这样。可以观摩《飘逸的python - 单例模式乱弹》

定义单例模式时,因为自定义的__new__重载了父类的__new__,所以要自己显式调用父类的__new__,即object.__new__(cls, *args, **kwargs),或者用super()。,不然就不是extend原来的实例了,而是替换原来的实例。

代码

import threading
class Signleton(object):
  def __init__(self):
    print("__init__ method called")
  def __new__(cls):
    print("__new__ method called")
    mutex=threading.Lock()
    mutex.acquire() # 上锁,防止多线程下出问题
    if not hasattr(cls, 'instance'):
      cls.instance = super(LogSignleton, cls).__new__(cls)
    mutex.release()
    return cls.instance 
if __name__ == '__main__':
obj = Signleton()

输出结果:

>>> ================================ RESTART ================================
>>>
__new__ method called
__init__ method called
>>>

说明

1.从输出结果来看,最先调用 __new__ 方法,然后调用__init__方法

2. __new__ 通常用于控制生成一个新实例的过程,它是类级别的方法。

3. __init__ 通常用于初始化一个新实例,控制这个初始化的过程,比如添加一些属性,做一些额外的操作,发生在类实例被创建完以后。它是实例级别的方法。

方法1

__new__ 在__init__初始化前,就已经实例化对象,可以利用这个方法实现单例模式。

print '----------------------方法1--------------------------' 
#方法1,实现__new__方法 
#并在将一个类的实例绑定到类变量_instance上, 
#如果cls._instance为None说明该类还没有实例化过,实例化该类,并返回 
#如果cls._instance不为None,直接返回cls._instance 
class Singleton(object): 
  def __new__(cls, *args, **kw): 
    if not hasattr(cls, '_instance'): 
      orig = super(Singleton, cls) 
      cls._instance = orig.__new__(cls, *args, **kw) 
    return cls._instance 
 
class MyClass(Singleton): 
  a = 1 
 
one = MyClass() 
two = MyClass() 
 
two.a = 3 
print one.a 
#3 
#one和two完全相同,可以用id(), ==, is检测 
print id(one) 
#29097904 
print id(two) 
#29097904 
print one == two 
#True 
print one is two 
#True

方法2

print '----------------------方法2--------------------------' 
#方法2,共享属性;所谓单例就是所有引用(实例、对象)拥有相同的状态(属性)和行为(方法) 
#同一个类的所有实例天然拥有相同的行为(方法), 
#只需要保证同一个类的所有实例具有相同的状态(属性)即可 
#所有实例共享属性的最简单最直接的方法就是__dict__属性指向(引用)同一个字典(dict) 
#可参看:http://code.activestate.com/recipes/66531/ 
class Borg(object): 
  _state = {} 
  def __new__(cls, *args, **kw): 
    ob = super(Borg, cls).__new__(cls, *args, **kw) 
    ob.__dict__ = cls._state 
    return ob 
 
class MyClass2(Borg): 
  a = 1 
 
one = MyClass2() 
two = MyClass2() 
 
#one和two是两个不同的对象,id, ==, is对比结果可看出 
two.a = 3 
print one.a 
#3 
print id(one) 
#28873680 
print id(two) 
#28873712 
print one == two 
#False 
print one is two 
#False 
#但是one和two具有相同的(同一个__dict__属性),见: 
print id(one.__dict__) 
#30104000 
print id(two.__dict__) 
#30104000

方法3

print '----------------------方法3--------------------------' 
#方法3:本质上是方法1的升级(或者说高级)版 
#使用__metaclass__(元类)的高级python用法 
class Singleton2(type): 
  def __init__(cls, name, bases, dict): 
    super(Singleton2, cls).__init__(name, bases, dict) 
    cls._instance = None 
  def __call__(cls, *args, **kw): 
    if cls._instance is None: 
      cls._instance = super(Singleton2, cls).__call__(*args, **kw) 
    return cls._instance 
 
class MyClass3(object): 
  __metaclass__ = Singleton2 
 
one = MyClass3() 
two = MyClass3() 
 
two.a = 3 
print one.a 
#3 
print id(one) 
#31495472 
print id(two) 
#31495472 
print one == two 
#True 
print one is two 
#True

方法4

print '----------------------方法4--------------------------' 
#方法4:也是方法1的升级(高级)版本, 
#使用装饰器(decorator), 
#这是一种更pythonic,更elegant的方法, 
#单例类本身根本不知道自己是单例的,因为他本身(自己的代码)并不是单例的 
def singleton(cls, *args, **kw): 
  instances = {} 
  def _singleton(): 
    if cls not in instances: 
      instances[cls] = cls(*args, **kw) 
    return instances[cls] 
  return _singleton 
 
@singleton 
class MyClass4(object): 
  a = 1 
  def __init__(self, x=0): 
    self.x = x 
 
one = MyClass4() 
two = MyClass4() 
 
two.a = 3 
print one.a 
#3 
print id(one) 
#29660784 
print id(two) 
#29660784 
print one == two 
#True 
print one is two 
#True 
one.x = 1 
print one.x 
#1 
print two.x

单例模式

单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。

比如,某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象。

在 Python 中,我们可以用多种方法来实现单例模式:

  • 使用模块
  • 使用 __new__
  • 使用装饰器(decorator)
  • 使用元类(metaclass)

使用模块

其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。如果我们真的想要一个单例类,可以考虑这样做:

# mysingleton.py
class My_Singleton(object):
  def foo(self):
    pass
 
my_singleton = My_Singleton()

将上面的代码保存在文件 mysingleton.py 中,然后这样使用:

from mysingleton import my_singleton
my_singleton.foo()

使用 __new__
为了使类只能出现一个实例,我们可以使用 __new__ 来控制实例的创建过程,代码如下:

class Singleton(object):
  _instance = None
  def __new__(cls, *args, **kw):
    if not cls._instance:
      cls._instance = super(Singleton, cls).__new__(cls, *args, **kw) 
    return cls._instance 
 
class MyClass(Singleton): 
  a = 1

在上面的代码中,我们将类的实例和一个类变量 _instance 关联起来,如果 cls._instance 为 None 则创建实例,否则直接返回 cls._instance。

执行情况如下:

>>> one = MyClass()
>>> two = MyClass()
>>> one == two
True
>>> one is two
True
>>> id(one), id(two)
(4303862608, 4303862608)

使用装饰器

我们知道,装饰器(decorator)可以动态地修改一个类或函数的功能。这里,我们也可以使用装饰器来装饰某个类,使其只能生成一个实例,代码如下:

from functools import wraps
 
def singleton(cls):
  instances = {}
  @wraps(cls)
  def getinstance(*args, **kw):
    if cls not in instances:
      instances[cls] = cls(*args, **kw)
    return instances[cls]
  return getinstance
 
@singleton
class MyClass(object):
  a = 1

在上面,我们定义了一个装饰器 singleton,它返回了一个内部函数 getinstance,该函数会判断某个类是否在字典 instances 中,如果不存在,则会将 cls 作为 key,cls(*args, **kw) 作为 value 存到 instances 中,否则,直接返回 instances[cls]。

使用 metaclass

元类(metaclass)可以控制类的创建过程,它主要做三件事:

  • 拦截类的创建
  • 修改类的定义
  • 返回修改后的类

使用元类实现单例模式的代码如下:

class Singleton(type):
  _instances = {}
  def __call__(cls, *args, **kwargs):
    if cls not in cls._instances:
      cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
    return cls._instances[cls]
 
# Python2
class MyClass(object):
  __metaclass__ = Singleton
 
# Python3
# class MyClass(metaclass=Singleton):
#  pass

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python里隐藏的“禅”
Jun 16 Python
详解Django+Uwsgi+Nginx的生产环境部署
Jun 25 Python
对python中dict和json的区别详解
Dec 18 Python
对Python发送带header的http请求方法详解
Jan 02 Python
Django项目中添加ldap登陆认证功能的实现
Apr 04 Python
Python使用random模块生成随机数操作实例详解
Sep 17 Python
python、Matlab求定积分的实现
Nov 20 Python
完美解决Django2.0中models下的ForeignKey()问题
May 19 Python
Keras设置以及获取权重的实现
Jun 19 Python
基于Python pyecharts实现多种图例代码解析
Aug 10 Python
Python多分支if语句的使用
Sep 03 Python
python实现sm2和sm4国密(国家商用密码)算法的示例
Sep 26 Python
django的ORM操作 增加和查询
Jul 26 #Python
Django在pycharm下修改默认启动端口的方法
Jul 26 #Python
Python解析命令行读取参数之argparse模块
Jul 26 #Python
Django Rest framework三种分页方式详解
Jul 26 #Python
浅析Windows 嵌入python解释器的过程
Jul 26 #Python
python flask几分钟实现web服务的例子
Jul 26 #Python
解决python flask中config配置管理的问题
Jul 26 #Python
You might like
消息持续发送的完整例子
2006/10/09 PHP
php绘制一个扇形的方法
2015/01/24 PHP
PHP yield关键字功能与用法分析
2019/01/03 PHP
ThinkPHP 5.x远程命令执行漏洞复现
2019/09/23 PHP
jquery Firefox3.5中操作select的问题
2009/07/10 Javascript
JS实现可改变列宽的table实例
2013/07/02 Javascript
浅谈JavaScript Date日期和时间对象
2014/12/29 Javascript
jQuery实现淡入淡出二级下拉导航菜单的方法
2015/08/28 Javascript
解析AngularJS中get请求URL出现的跨域问题
2016/12/01 Javascript
js 单引号替换成双引号,双引号替换成单引号的实现方法
2017/02/16 Javascript
JS实现的自动打字效果示例
2017/03/10 Javascript
深入浅析javascript函数中with
2018/10/28 Javascript
vue计算属性computed的使用方法示例
2019/03/13 Javascript
解决JQuery的ajax函数执行失败alert函数弹框一闪而过问题
2019/04/10 jQuery
ant design中upload组件上传大文件,显示进度条进度的实例
2020/10/29 Javascript
[51:50]完美世界DOTA2联赛 Magma vs GXR 第一场 11.07
2020/11/10 DOTA
python 示例分享---逻辑推理编程解决八皇后
2014/07/20 Python
python实现的用于搜索文件并进行内容替换的类实例
2015/06/28 Python
selenium+python环境配置教程详解
2019/05/28 Python
python标准库OS模块详解
2020/03/10 Python
Python celery原理及运行流程解析
2020/06/13 Python
python统计mysql数据量变化并调用接口告警的示例代码
2020/09/21 Python
python获取命令行参数实例方法讲解
2020/11/02 Python
最新版 Windows10上安装Python 3.8.5的步骤详解
2020/11/28 Python
手对手的教你用canvas画一个简单的海报的方法示例
2018/12/10 HTML / CSS
Luxplus瑞典:香水和美容护理折扣
2018/01/28 全球购物
在职人员函授期间自我评价分享
2013/11/08 职场文书
小学生成长感言
2014/01/30 职场文书
小班秋游活动方案
2014/02/22 职场文书
动员大会主持词
2014/03/20 职场文书
奥巴马的演讲稿
2014/05/15 职场文书
大学生应聘导游自荐信
2014/06/02 职场文书
党支部遵守党的政治纪律情况对照检查材料
2014/09/26 职场文书
学习保证书100字
2015/02/26 职场文书
2016机关干部作风建设心得体会
2016/01/21 职场文书
浅谈JS的原型和原型链
2021/06/04 Javascript