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根据经纬度计算距离示例
Feb 16 Python
Python装饰器decorator用法实例
Nov 10 Python
用python写的一个wordpress的采集程序
Feb 27 Python
Python打造出适合自己的定制化Eclipse IDE
Mar 02 Python
浅谈python中字典append 到list 后值的改变问题
May 04 Python
python3爬虫之设计签名小程序
Jun 19 Python
python 3调用百度OCR API实现剪贴板文字识别
Sep 04 Python
Python线程之定位与销毁的实现
Feb 17 Python
Python爬取数据保存为Json格式的代码示例
Apr 09 Python
python求质数列表的例子
Nov 24 Python
python Selenium 库的使用技巧
Oct 16 Python
基于tensorflow __init__、build 和call的使用小结
Feb 26 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
火车头discuz6.1 完美采集的php接口文件
2009/09/13 PHP
PHP session会话的安全性分析
2011/09/08 PHP
php中通过curl smtp发送邮件
2012/06/05 PHP
Centos6.5和Centos7 php环境搭建方法
2016/05/27 PHP
如何判断php mysqli扩展类是否开启
2016/12/24 PHP
基于JQuery的访问WebService的代码(可访问Java[Xfire])
2010/11/19 Javascript
JS文本框不能输入空格验证方法
2013/03/19 Javascript
JQuery操作iframe父页面与子页面的元素与方法(实例讲解)
2013/11/20 Javascript
nodejs教程之制作一个简单的文章发布系统
2014/11/21 NodeJs
jQuery表单美化插件jqTransform使用详解
2015/04/12 Javascript
jQuery 判断图片是否加载完成方法汇总
2015/08/10 Javascript
js实现继承的5种方式
2015/12/01 Javascript
JavaScript获取当前cpu使用率的方法
2015/12/15 Javascript
Bootstrap实现渐变顶部固定自适应导航栏
2020/08/27 Javascript
loading动画特效小结
2017/01/22 Javascript
nodejs入门教程六:express模块用法示例
2017/04/24 NodeJs
JavaScript requestAnimationFrame动画详解
2017/09/14 Javascript
小程序实现搜索框
2020/06/19 Javascript
Python减少循环层次和缩进的技巧分析
2016/03/15 Python
Windows平台Python连接sqlite3数据库的方法分析
2017/07/12 Python
python pands实现execl转csv 并修改csv指定列的方法
2018/12/12 Python
Python动态语言与鸭子类型详解
2019/07/01 Python
详解numpy矩阵的创建与数据类型
2019/10/18 Python
Pandas DataFrame中的tuple元素遍历的实现
2019/10/23 Python
python实现图片横向和纵向拼接
2020/03/05 Python
Python3 利用face_recognition实现人脸识别的方法
2020/03/13 Python
Python如何telnet到网络设备
2021/02/18 Python
CSS3 calc()会计算属性详解
2018/02/27 HTML / CSS
英国设计师泳装、沙滩装和比基尼在线精品店:Beach Cafe
2019/08/28 全球购物
掌上明珠Java程序员面试总结
2016/02/23 面试题
毕业生自荐书模版
2014/01/04 职场文书
新年团拜会主持词
2014/04/02 职场文书
开工典礼策划方案
2014/05/23 职场文书
军训个人总结
2015/03/03 职场文书
公司文体活动总结
2015/05/07 职场文书
《蜜蜂引路》教学反思
2016/02/22 职场文书