Python设计模式中单例模式的实现及在Tornado中的应用


Posted in Python onMarch 02, 2016

单例模式的实现方式
将类实例绑定到类变量上

class Singleton(object):
  _instance = None

  def __new__(cls, *args):
    if not isinstance(cls._instance, cls):
      cls._instance = super(Singleton, cls).__new__(cls, *args)
    return cls._instance

但是子类在继承后可以重写__new__以失去单例特性

class D(Singleton):

  def __new__(cls, *args):
    return super(D, cls).__new__(cls, *args)

使用装饰器实现

def singleton(_cls):
  inst = {}

  def getinstance(*args, **kwargs):
    if _cls not in inst:
      inst[_cls] = _cls(*args, **kwargs)
    return inst[_cls]
  return getinstance

@singleton
class MyClass(object):
  pass

问题是这样装饰以后返回的不是类而是函数,当然你可以singleton里定义一个类来解决问题,但这样就显得很麻烦了

使用__metaclass__,这个方式最推荐

class Singleton(type):
  _inst = {}
  
  def __call__(cls, *args, **kwargs):
    if cls not in cls._inst:
      cls._inst[cls] = super(Singleton, cls).__call__(*args)
    return cls._inst[cls]


class MyClass(object):
  __metaclass__ = Singleton

Tornado中的单例模式运用
来看看tornado.IOLoop中的单例模式:

class IOLoop(object):

  @staticmethod
  def instance():
    """Returns a global `IOLoop` instance.

Most applications have a single, global `IOLoop` running on the
main thread. Use this method to get this instance from
another thread. To get the current thread's `IOLoop`, use `current()`.
"""
    if not hasattr(IOLoop, "_instance"):
      with IOLoop._instance_lock:
        if not hasattr(IOLoop, "_instance"):
          # New instance after double check
          IOLoop._instance = IOLoop()
    return IOLoop._instance

为什么这里要double check?来看个这里面简单的单例模式,先来看看代码:

class Singleton(object):

  @staticmathod
  def instance():
    if not hasattr(Singleton, '_instance'):
      Singleton._instance = Singleton()
    return Singleton._instance

在 Python 里,可以在真正的构造函数__new__里做文章:

class Singleton(object):

  def __new__(cls, *args, **kwargs):
    if not hasattr(cls, '_instance'):
      cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
    return cls._instance

这种情况看似还不错,但是不能保证在多线程的环境下仍然好用,看图:

Python设计模式中单例模式的实现及在Tornado中的应用

出现了多线程之后,这明显就是行不通的。

1.上锁使线程同步
上锁后的代码:

import threading

class Singleton(object):

  _instance_lock = threading.Lock()
  
  @staticmethod
  def instance():
    with Singleton._instance_lock:
      if not hasattr(Singleton, '_instance'):
        Singleton._instance = Singleton()
    return Singleton._instance

这里确实是解决了多线程的情况,但是我们只有实例化的时候需要上锁,其它时候Singleton._instance已经存在了,不需要锁了,但是这时候其它要获得Singleton实例的线程还是必须等待,锁的存在明显降低了效率,有性能损耗。

2.全局变量
在 Java/C++ 这些语言里还可以利用全局变量的方式解决上面那种加锁(同步)带来的问题:

class Singleton {

  private static Singleton instance = new Singleton();
  
  private Singleton() {}
  
  public static Singleton getInstance() {
    return instance;
  }
  
}

在 Python 里就是这样了:

class Singleton(object):

  @staticmethod
  def instance():
    return _g_singleton

_g_singleton = Singleton()

# def get_instance():
# return _g_singleton

但是如果这个类所占的资源较多的话,还没有用这个实例就已经存在了,是非常不划算的,Python 代码也略显丑陋……

所以出现了像tornado.IOLoop.instance()那样的double check的单例模式了。在多线程的情况下,既没有同步(加锁)带来的性能下降,也没有全局变量直接实例化带来的资源浪费。

3.装饰器

如果使用装饰器,那么将会是这样:

import functools

def singleton(cls):
  ''' Use class as singleton. '''

  cls.__new_original__ = cls.__new__

  @functools.wraps(cls.__new__)
  def singleton_new(cls, *args, **kw):
    it = cls.__dict__.get('__it__')
    if it is not None:
      return it

    cls.__it__ = it = cls.__new_original__(cls, *args, **kw)
    it.__init_original__(*args, **kw)
    return it

  cls.__new__ = singleton_new
  cls.__init_original__ = cls.__init__
  cls.__init__ = object.__init__

  return cls

#
# Sample use:
#

@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
def singleton(cls):
  instance = cls()
  instance.__call__ = lambda: instance
  return instance

#
# Sample use
#

@singleton
class Highlander:
  x = 100
  # Of course you can have any attributes or methods you like.

Highlander() is Highlander() is Highlander #=> True
id(Highlander()) == id(Highlander) #=> True
Highlander().x == Highlander.x == 100 #=> True
Highlander.x = 50
Highlander().x == Highlander.x == 50 #=> True
Python 相关文章推荐
将Python中的数据存储到系统本地的简单方法
Apr 11 Python
python实战教程之自动扫雷
Jul 13 Python
Python退火算法在高次方程的应用
Jul 26 Python
python使用xlrd和xlwt读写Excel文件的实例代码
Sep 05 Python
Python中logging实例讲解
Jan 17 Python
Python JSON格式数据的提取和保存的实现
Mar 22 Python
Django 迁移、操作数据库的方法
Aug 02 Python
Python 使用指定的网卡发送HTTP请求的实例
Aug 21 Python
python使用opencv在Windows下调用摄像头实现解析
Nov 26 Python
django框架基于queryset和双下划线的跨表查询操作详解
Dec 11 Python
python文件和文件夹复制函数
Feb 07 Python
pyecharts绘制中国2020肺炎疫情地图的实例代码
Feb 12 Python
Python使用设计模式中的责任链模式与迭代器模式的示例
Mar 02 #Python
详解Python设计模式编程中观察者模式与策略模式的运用
Mar 02 #Python
Python设计模式编程中解释器模式的简单程序示例分享
Mar 02 #Python
分析Python中设计模式之Decorator装饰器模式的要点
Mar 02 #Python
实例解析Python设计模式编程之桥接模式的运用
Mar 02 #Python
Python随机生成带特殊字符的密码
Mar 02 #Python
Python设计模式编程中Adapter适配器模式的使用实例
Mar 02 #Python
You might like
优化NFR之一 --MSSQL Hello Buffer Overflow
2006/10/09 PHP
pw的一个放后门的方法分析
2007/10/08 PHP
攻克CakePHP系列一 连接MySQL数据库
2008/10/22 PHP
PHP实现Soap通讯的方法
2014/11/03 PHP
PHP中的命名空间相关概念浅析
2015/01/22 PHP
Yii框架where查询用法实例分析
2019/10/22 PHP
PHP配合fiddler抓包抓取微信指数小程序数据的实现方法分析
2020/01/02 PHP
JS前端框架关于重构的失败经验分享
2013/03/17 Javascript
JavaScript中的onerror事件概述及使用
2013/04/01 Javascript
最新最热最实用的15个jQuery插件汇总
2015/07/05 Javascript
省市区三级联动下拉框菜单javascript版
2015/08/11 Javascript
jQuery实现点击小图片淡入淡出显示大图片特效
2015/09/09 Javascript
js实现新年倒计时效果
2015/12/10 Javascript
JavaScript tab选项卡插件实例代码
2016/02/23 Javascript
BootStrap 轮播插件(carousel)支持左右手势滑动的方法(三种)
2016/07/07 Javascript
jQuery插件EasyUI设置datagrid的checkbox为禁用状态的方法
2016/08/05 Javascript
JavaScript通过改变文字透明度实现的文字闪烁效果实例
2017/04/27 Javascript
微信小程序对接七牛云存储的方法
2017/07/30 Javascript
微信小程序实现图片压缩功能
2018/01/26 Javascript
Vue 莹石摄像头直播视频实例代码
2018/08/31 Javascript
vue 指令之气泡提示效果的实现代码
2018/10/18 Javascript
创建Vue项目以及引入Iview的方法示例
2018/12/03 Javascript
Angular6 发送手机验证码按钮倒计时效果实现方法
2019/01/08 Javascript
Vue利用Blob下载原生二进制数组文件
2019/09/25 Javascript
vue 获取及修改store.js里的公共变量实例
2019/11/06 Javascript
Vue打包后访问静态资源路径问题
2019/11/08 Javascript
Vue-cli打包后部署到子目录下的路径问题说明
2020/09/02 Javascript
Python获取linux主机ip的简单实现方法
2016/04/18 Python
Python遍历文件夹和读写文件的实现方法
2017/05/10 Python
Python3爬虫中识别图形验证码的实例讲解
2020/07/30 Python
Python 列表推导式需要注意的地方
2020/10/23 Python
J2ee常用的设计模式?说明工厂模式
2015/05/21 面试题
爱的承诺书
2015/01/20 职场文书
客房服务员岗位职责
2015/02/09 职场文书
2015年全国助残日活动方案
2015/05/04 职场文书
详解 TypeScript 枚举类型
2021/11/02 Javascript