python单例模式与metaclass
admin
2023-07-31 01:44:20
0

单例模式的实现方式

将类实例绑定到类变量上

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: \’Foo\’ () {\’__module__\’: \’__main__\’, \’__metaclass__\’: , \’__init__\’: init at 0x106fd5320>}
init: \’Foo\’ () {\’__module__\’: \’__main__\’, \’__metaclass__\’: , \’__init__\’: init at 0x106fd5320>}
call: (\’a\’,)
init: <__main__.Foo object at 0x106fee990> (\’a\’,) {}
call: (\’b\’,)
init: <__main__.Foo object at 0x106feea50> (\’b\’,) {}

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

相关内容

热门资讯

Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...
项目管理和工程管理的区别 项目管理 项目管理,顾名思义就是专注于开发和完成项目的管理,以实现目标并满足成功标准和项目要求。 工...