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登录Dr.com思路以及代码分享
Jun 25 Python
为python设置socket代理的方法
Jan 14 Python
简单介绍Python中的round()方法
May 15 Python
python函数装饰器用法实例详解
Jun 04 Python
Python实现监控程序执行时间并将其写入日志的方法
Jun 30 Python
基于python 二维数组及画图的实例详解
Apr 03 Python
Python实现注册、登录小程序功能
Sep 21 Python
浅谈python3发送post请求参数为空的情况
Dec 28 Python
python3转换code128条形码的方法
Apr 17 Python
new_zeros() pytorch版本的转换方式
Feb 18 Python
Python函数默认参数常见问题及解决方案
Mar 26 Python
python matplotlib工具栏源码探析三之添加、删除自定义工具项的案例详解
Feb 25 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封装的数据库函数与用法示例【参考thinkPHP】
2016/11/08 PHP
解析window.open的使用方法总结
2013/06/19 Javascript
showModalDialog在谷歌浏览器下会返回Null的解决方法
2013/11/27 Javascript
JS实现点击链接取消跳转效果的方法
2014/01/24 Javascript
让JavaScript和其它资源并发下载的方法
2014/10/16 Javascript
使用jquery 简单实现下拉菜单
2015/01/14 Javascript
jQuery实现导航回弹效果
2017/02/27 Javascript
react 创建单例组件的方法
2018/04/26 Javascript
webuploader实现上传图片到服务器功能
2018/08/16 Javascript
JavaScript中的一些实用小技巧总结
2019/04/07 Javascript
浅谈Vue为什么不能检测数组变动
2019/10/14 Javascript
vue $set 给数据赋值的实例
2019/11/09 Javascript
vue项目中使用vue-layer弹框插件的方法
2020/03/11 Javascript
vue 实现超长文本截取,悬浮框提示
2020/07/29 Javascript
Python实现全角半角转换的方法
2014/08/18 Python
Python生成不重复随机值的方法
2015/05/11 Python
利用Python获取赶集网招聘信息前篇
2016/04/18 Python
浅谈Python爬取网页的编码处理
2016/11/04 Python
Python基于list的append和pop方法实现堆栈与队列功能示例
2017/07/24 Python
python画出三角形外接圆和内切圆的方法
2018/01/25 Python
Django权限机制实现代码详解
2018/02/05 Python
使用django-crontab实现定时任务的示例
2018/02/26 Python
Python中is和==的区别详解
2018/11/15 Python
Python中最大递归深度值的探讨
2019/03/05 Python
python实现雪花飘落效果实例讲解
2019/06/18 Python
Pycharm运行加载文本出现错误的解决方法
2019/06/27 Python
Python面向对象之继承原理与用法案例分析
2019/12/31 Python
Python的赋值、深拷贝与浅拷贝的区别详解
2020/02/12 Python
美津浓美国官网:Mizuno美国
2018/08/07 全球购物
Interflora澳大利亚:同日鲜花速递
2019/06/25 全球购物
linux面试题参考答案(5)
2014/09/01 面试题
美发店5.1活动方案
2014/01/24 职场文书
2015年社区卫生工作总结
2015/04/21 职场文书
首次购房证明
2015/06/19 职场文书
大学生读书笔记大全
2015/07/01 职场文书
MySQL kill不掉线程的原因
2021/05/07 MySQL