python单例模式与metaclass

单例模式的实现方式

将类实例绑定到类变量上

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__方法


兴趣在程序语言, 高性能, 分布式等方面

761 声望
44 粉丝
0 条评论
推荐阅读
记一次网速不佳的排查过程
网速不佳的常见情况, 包括以下几种: 路由器质量差, 处理芯片差/过热/天线信号差等, 都会导致数据传输会变慢 运营商实体网线有问题, 最坑最不好排查的情况, 曾经碰到过出户网线部分断裂的情况 运营商服务问题, 包...

quietin阅读 1.2k

Ubuntu20.04 从源代码编译安装 python3.10
Ubuntu 22.04 Release DateUbuntu 22.04 Jammy Jellyfish is scheduled for release on April 21, 2022If you’re ready to use Ubuntu 22.04 Jammy Jellyfish, you can either upgrade your current Ubuntu syste...

ponponon1阅读 4k评论 1

日常Python 代码片段整理
1、简单的 HTTP Web 服务器 {代码...} 2、单行循环List {代码...} 3、更新字典 {代码...} 4、拆分多行字符串 {代码...} 5、跟踪列表中元素的频率 {代码...} 6、不使用 Pandas 读取 CSV 文件 {代码...} 7、将列表...

墨城2阅读 332

Unicode 正则表达式(qbit)
前言本文根据《精通正则表达式》和 Unicode Regular Expressions 整理。本文的示例默认以 Python3 为实现语言,用到 Python3 的 re 模块或 regex 库。基本的 Unicode 属性分类 {代码...} 基本的 Unicode 子属性Le...

qbit阅读 4.4k

Python + Sqlalchemy 对数据库的批量插入或更新(Upsert)
由于不同数据库对这种 upsert 的实现机制不同,Sqlalchemy 也就不再试图做一致性的封装了,而是提供了各自的方言 API,具体到 Mysql,就是给 insert statement ,增加了 on_duplicate_key_update 方法。

songofhawk1阅读 2k评论 4

封面图
打脸了兄弟们,Go1.20 arena 来了!
大家好,我是煎鱼。大概半年前,我写过一篇文章《Go 要违背初心吗?新提案:手动管理内存》。有兴趣了深入解的同学,可以再回顾一下。当时我们还想着 Go 团队应该不会接纳,至少不会那么快:懒得翻也可以看我再次...

煎鱼1阅读 3.3k

uwsgi 注意事项
http 和 http-socket 选项是完全不同的。第一个生成一个额外的进程,转发请求到一系列的worker (将它想象为一种形式的盾牌,与apache或者nginx同级),而第二个设置worker为原生使用http协议。

zed2015阅读 2.2k

兴趣在程序语言, 高性能, 分布式等方面

761 声望
44 粉丝
宣传栏