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 相关文章推荐
django实现分页的方法
May 26 Python
django使用图片延时加载引起后台404错误
Apr 18 Python
python递归打印某个目录的内容(实例讲解)
Aug 30 Python
python保存文件方法小结
Jul 27 Python
python去重,一个由dict组成的list的去重示例
Jan 21 Python
Python学习笔记之读取文件、OS模块、异常处理、with as语法示例
Jun 04 Python
Python 生成一个从0到n个数字的列表4种方法小结
Nov 28 Python
简单了解python装饰器原理及使用方法
Dec 18 Python
python怎么判断模块安装完成
Jun 19 Python
详解python安装matplotlib库三种失败情况
Jul 28 Python
Django数据库迁移常见使用方法
Nov 12 Python
python中翻译功能translate模块实现方法
Dec 17 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.ini实现Mysql导入数据库文件最大限制的修改方法
2007/12/11 PHP
PHP 引用是个坏习惯
2010/03/12 PHP
php常量详细解析
2015/10/27 PHP
如何使用JS获取IE上传文件路径(IE7,8)
2013/07/08 Javascript
在js文件中如何获取basePath处理js路径问题
2013/07/10 Javascript
js function定义函数的几种不错方法
2014/02/27 Javascript
ie9 提示'console' 未定义问题的解决方法
2014/03/20 Javascript
Javascript实现多彩雪花从天降散落效果的方法
2015/02/02 Javascript
js实现两点之间画线的方法
2015/05/12 Javascript
Javascript之面向对象--接口
2016/12/02 Javascript
100行代码理解和分析vue2.0响应式架构
2017/03/09 Javascript
js实现抽奖效果
2017/03/27 Javascript
纯js实现动态时间显示
2020/09/07 Javascript
Vue列表渲染的示例代码
2018/11/01 Javascript
解决vue打包后vendor.js文件过大问题
2019/07/03 Javascript
Vue 刷新当前路由的实现代码
2019/09/26 Javascript
解决vue的过渡动画无法正常实现问题
2019/10/31 Javascript
VUE项目axios请求头更改Content-Type操作
2020/07/24 Javascript
vue-router懒加载的3种方式汇总
2021/02/28 Vue.js
[01:38]【DOTA2亚洲邀请赛】Sumail——梦开始的地方
2017/03/03 DOTA
python中的闭包函数
2018/02/09 Python
Python实现定时备份mysql数据库并把备份数据库邮件发送
2018/03/08 Python
pycharm设置注释颜色的方法
2018/05/23 Python
django 中的聚合函数,分组函数,F 查询,Q查询
2019/07/25 Python
python爬虫 Pyppeteer使用方法解析
2019/09/28 Python
浅谈python处理json和redis hash的坑
2020/07/16 Python
SVG实现多彩圆环倒计时效果的示例代码
2017/11/21 HTML / CSS
英国最大的女性服装零售商:Dorothy Perkins
2017/03/30 全球购物
食品安全汇报材料
2014/08/18 职场文书
2014年体育工作总结
2014/11/24 职场文书
上下班时间调整通知
2015/04/23 职场文书
2015年资料员工作总结
2015/04/25 职场文书
3招让你摆脱即兴讲话冷场尴尬
2019/08/08 职场文书
python某漫画app逆向
2021/03/31 Python
Python字符串的转义字符
2022/04/07 Python
MySQL 字符集 character
2022/05/04 MySQL