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处理csv数据的方法
Mar 11 Python
在Python中使用zlib模块进行数据压缩的教程
Jun 26 Python
Python IDLE 错误:IDLE''s subprocess didn''t make connection 的解决方案
Feb 13 Python
python批量读取txt文件为DataFrame的方法
Apr 03 Python
Django1.9 加载通过ImageField上传的图片方法
May 25 Python
利用python循环创建多个文件的方法
Oct 25 Python
在unittest中使用 logging 模块记录测试数据的方法
Nov 30 Python
几个适合python初学者的简单小程序,看完受益匪浅!(推荐)
Apr 16 Python
Python列表(list)所有元素的同一操作解析
Aug 01 Python
使用jupyter notebook运行python和R的步骤
Aug 13 Python
python爬取豆瓣电影TOP250数据
May 23 Python
 分享一个Python 遇到数据库超好用的模块
Apr 06 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
php strcmp使用说明
2010/04/22 PHP
php ZipArchive压缩函数详解实例
2013/11/06 PHP
PHP中使用php5-ffmpeg撷取视频图片实例
2015/01/07 PHP
Ubuntu12下编译安装PHP5.3开发环境
2015/03/27 PHP
PHP树-不需要递归的实现方法
2016/06/21 PHP
php获取微信共享收货地址的方法
2017/12/21 PHP
Laravel框架实现多数据库连接操作详解
2019/07/12 PHP
laravel添加前台跳转成功页面示例
2019/10/22 PHP
JavaScript 无符号右移运算符
2009/04/17 Javascript
使用jQuery向asp.net Mvc传递复杂json数据-ModelBinder篇
2010/05/07 Javascript
关于javascript中的typeof和instanceof介绍
2012/12/04 Javascript
JS保存、读取、换行、转Json报错处理方法
2013/06/14 Javascript
如何实现修改密码时密码框显示保存到cookie的密码
2013/12/10 Javascript
JavaScript基础知识学习笔记
2014/12/02 Javascript
javascript学习笔记之函数定义
2015/06/25 Javascript
nodejs爬虫抓取数据之编码问题
2015/07/03 NodeJs
jQuery使用cookie与json简单实现购物车功能
2016/04/15 Javascript
原生JavaScript编写canvas版的连连看游戏
2016/05/29 Javascript
jQuery 中ajax异步调用的四种方式
2016/06/28 Javascript
浅谈键盘上回车按钮的js触发事件
2017/02/13 Javascript
vue2.0实现分页组件的实例代码
2017/06/22 Javascript
React 子组件向父组件传值的方法
2017/07/24 Javascript
关于vue中watch检测到不到对象属性的变化的解决方法
2018/02/08 Javascript
angularjs 获取默认选中的单选按钮的value方法
2018/02/28 Javascript
Vue前后端不同端口的实现方法
2018/09/19 Javascript
JavaScript常用事件介绍
2019/01/21 Javascript
swiper实现异形轮播效果
2019/11/28 Javascript
vue-router之解决addRoutes使用遇到的坑
2020/07/19 Javascript
Python实现获取网站PR及百度权重
2015/01/21 Python
Python获取本机所有网卡ip,掩码和广播地址实例代码
2018/01/22 Python
Django admin管理工具TabularInline类用法详解
2020/05/14 Python
国际商务专业求职信
2014/07/15 职场文书
借款协议书
2014/09/16 职场文书
财务人员岗位职责
2015/02/03 职场文书
欧也妮葛朗台读书笔记
2015/06/30 职场文书
JS不要再到处使用绝对等于运算符了
2021/04/30 Javascript