详解python单例模式与metaclass


Posted in Python onJanuary 15, 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

metaclass

元类就是用来创建类的东西,可以简单把元类称为“类工厂”,类是元类的实例。type就是Python的内建元类,type也是自己的元类,任何一个类

>>> type(MyClass)
type
>>> type(type)
type

python在创建类MyClass的过程中,会在类的定义中寻找__metaclass__,如果存在则用其创建类MyClass,否则使用内建的type来创建类。对于类有继承的情况,如果当前类没有找到,会继续在父类中寻找__metaclass__,直到所有父类中都没有找到才使用type创建类。
如果模块里有__metaclass__的全局变量的话,其中的类都将以其为元类,亲自试了,没这个作用,无任何影响

查看type的定义,

type(object) -> the object's type
type(name, bases, dict) -> a new type

所以利用type定义一个类的元类,可以用函数返回一个上面第二种定义的对象,也可以继承type并重写其中的方法。

直接使用type生成的对象作为元类,函数作用是使属性变为大写

def update_(name, bases, dct):
  attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
  uppercase_attr = {name.upper(): value for name, value in attrs}
  return type(name, bases, uppercase_attr)


class Singleton(object):
  __metaclass__ = update_
  abc = 2

d = Singleton()
print d.ABC
# 2

上一节中,单例模式元类实现用的是类继承方式,而对于第一种__new__的方式,本质上调用的是type.__new__,不过使用super能使继承更清晰一些并避免一些问题

这里简单说明一下,__new__是在__init__前调用的方法,会创建对象并返回,而__init__则是用传入的参数将对象初始化。看一下type中这两者以及__call__的实现

def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
    """
    type(object) -> the object's type
    type(name, bases, dict) -> a new type
    # (copied from class doc)
    """
    pass

@staticmethod # known case of __new__
def __new__(S, *more): # real signature unknown; restored from __doc__
  """ T.__new__(S, ...) -> a new object with type S, a subtype of T """
  pass

def __call__(self, *more): # real signature unknown; restored from __doc__
  """ x.__call__(...) <==> x(...) """
  pass

前面提到类相当于元类的实例化,再联系创建单例模式时使用的函数,用的是__call__,其实用三种magic method中任何一种都是可以的,来看一下使用元类时各方法的调用情况

class Basic(type):
  def __new__(cls, name, bases, newattrs):
    print "new: %r %r %r %r" % (cls, name, bases, newattrs)
    return super(Basic, cls).__new__(cls, name, bases, newattrs)

  def __call__(self, *args):
    print "call: %r %r" % (self, args)
    return super(Basic, self).__call__(*args)

  def __init__(cls, name, bases, newattrs):
    print "init: %r %r %r %r" % (cls, name, bases, newattrs)
    super(Basic, cls).__init__(name, bases, dict)


class Foo:
  __metaclass__ = Basic

  def __init__(self, *args, **kw):
    print "init: %r %r %r" % (self, args, kw)

a = Foo('a')
b = Foo('b')

结果

new: <class '__main__.Basic'> 'Foo' () {'__module__': '__main__', '__metaclass__': <class '__main__.Basic'>, '__init__': <function init at 0x106fd5320>}
init: <class '__main__.Foo'> 'Foo' () {'__module__': '__main__', '__metaclass__': <class '__main__.Basic'>, '__init__': <function init at 0x106fd5320>}
call: <class '__main__.Foo'> ('a',)
init: <__main__.Foo object at 0x106fee990> ('a',) {}
call: <class '__main__.Foo'> ('b',)
init: <__main__.Foo object at 0x106feea50> ('b',) {}

元类的__init__和__new__只在创建类Foo调用了一次,而创建Foo的实例时,每次都会调用元类的__call__方法

以上就是本文的全部内容,对python单例模式与metaclass进行了描述,希望对大家的学习有所帮助。

Python 相关文章推荐
Python实现提取谷歌音乐搜索结果的方法
Jul 10 Python
python opencv设置摄像头分辨率以及各个参数的方法
Apr 02 Python
Python利用公共键如何对字典列表进行排序详解
May 19 Python
对numpy Array [: ,] 的取值方法详解
Jul 02 Python
用Python爬取QQ音乐评论并制成词云图的实例
Aug 24 Python
Anaconda 查看、创建、管理和使用python环境的方法
Dec 03 Python
PyTorch里面的torch.nn.Parameter()详解
Jan 03 Python
jupyter notebook参数化运行python方式
Apr 10 Python
基于Python测试程序是否有错误
May 16 Python
selenium+headless chrome爬虫的实现示例
Jan 08 Python
python os.listdir()乱码解决方案
Jan 31 Python
Python实现简单猜数字游戏
Feb 03 Python
理解python正则表达式
Jan 15 #Python
Python工程师面试题 与Python Web相关
Jan 14 #Python
Python工程师面试题 与Python基础语法相关
Jan 14 #Python
5种Python单例模式的实现方式
Jan 14 #Python
Python2.x与Python3.x的区别
Jan 14 #Python
python Django模板的使用方法
Jan 14 #Python
Python数据类型学习笔记
Jan 13 #Python
You might like
phpMyAdmin 链接表的附加功能尚未激活问题的解决方法(已测)
2012/03/27 PHP
在win系统安装配置 Memcached for PHP 5.3 图文教程
2015/03/03 PHP
简单谈谈PHP vs Node.js
2015/07/17 PHP
php foreach如何跳出两层循环(详解)
2016/11/05 PHP
PHP面向对象程序设计之对象克隆clone和魔术方法__clone()用法分析
2019/06/12 PHP
laravel Validator ajax返回错误信息的方法
2019/09/29 PHP
PHP SESSION跨页面传递失败解决方案
2020/12/11 PHP
(function(){})()的用法与优点
2007/03/11 Javascript
网页中实现浏览器的最大,最小化和关闭按钮
2007/03/12 Javascript
jQuery避免$符和其他JS库冲突的方法对比
2014/02/20 Javascript
jquery根据一个值来选中select下的option实例代码
2016/08/29 Javascript
JS动态给对象添加属性和值的实现方法
2016/10/21 Javascript
jquery移除了live()、die(),新版事件绑定on()、off()的方法
2016/10/26 Javascript
jQuery select自动选中功能实现方法分析
2016/11/28 Javascript
jQuery插件版本冲突的处理方法分析
2017/01/16 Javascript
bootstrap模态框远程示例代码分享
2017/05/22 Javascript
关于axios如何全局注册浅析
2018/01/14 Javascript
解决angular双向绑定无效果,ng-model不能正常显示的问题
2018/10/02 Javascript
详解webpack之图片引入-增强的file-loader:url-loader
2018/10/08 Javascript
es6 super关键字的理解与应用实例分析
2020/02/15 Javascript
[01:16:50]DOTA2-DPC中国联赛 正赛 Phoenix vs CDEC BO3 第一场 3月7日
2021/03/11 DOTA
Python入门_浅谈for循环、while循环
2017/05/16 Python
Python如何快速上手? 快速掌握一门新语言的方法
2017/11/14 Python
Python简单实现查找一个字符串中最长不重复子串的方法
2018/03/26 Python
对Python中for复合语句的使用示例讲解
2018/11/01 Python
Python使用crontab模块设置和清除定时任务操作详解
2019/04/09 Python
python getpass实现密文实例详解
2019/09/24 Python
PyTorch加载预训练模型实例(pretrained)
2020/01/17 Python
详解如何获取localStorage最大存储大小的方法
2020/05/21 HTML / CSS
小学生读书感言
2014/02/12 职场文书
常务副县长“三严三实”对照检查材料思想汇报
2014/10/05 职场文书
领导干部整治奢华浪费之风思想汇报
2014/10/07 职场文书
试用期自我评价怎么写
2015/03/10 职场文书
2015年公司工作总结
2015/04/25 职场文书
《赵州桥》教学反思
2016/02/17 职场文书
Redis入门教程详解
2021/08/30 Redis