• Python 元类


    印象中,是在创建单例模式时知道可以用到元类(metaclass),但始终对其了解的不是很透彻,很多人也都说元类是Python中较难理解的概念之一,于是找来几本书,希望可以找到答案,本文以Python3为例。

    本文参考:

    《人人都懂设计模式》

    《Python Cookbook》

    《 流畅的Python》

    先来简单介绍下:元类(metaclass)是一个类,你也可以理解为类的类,因为Python中的类是在运行时动态创建的,那么通过元类便可以控制类属性和类实例的创建过程。

    来看看用元类实现的单例模式:

    class Singleton(type):
        """
        单例模式
        """
        def __init__(cls, *args, **kwargs):
            cls.__instance = None
            super().__init__(*args, **kwargs)
    
        def __call__(cls, *args, **kwargs):
            if cls.__instance is None:
                cls.__instance = super().__call__(*args, **kwargs)
            return cls.__instance
    
    
    class Test(metaclass=Singleton):
    
        def __init__(self):
            pass
    
    
    a = Test()
    b = Test()
    print(id(a))
    print(id(b))
    

    具体的实现是:创建类时显式的指定类的metaclass,而自定义的metaclass继承type,并重新实现__call__方法。

    于是,有了两个问题:

    • 为什么自定义的metaclass继承type?

    因为,在Python中,type是默认的metaclass(内建元类),Python允许我们自定义metaclass,自定义的metaclass必须继承自type,也就是:元类从type类继承了构建类的能力。

    我们通常用type来获取对象所属的类,就像这样:

    In [10]: a = 10
    
    In [11]: type(a)
    Out[11]: int
    

    然而,type还是一个类,你可以通过type来新建一个类,看type的源码,通过type(name, bases, dict)便可以生成一个新的类:

    In [44]: test_class = type('Test', (), dict({'name': None}))
    
    In [45]: a = test_class()
    
    In [46]: a.name = 'Tony'
    
    In [47]: a.name
    Out[47]: 'Tony'
    

    默认情况下,Python中类都是type类的实例:

    In [12]: class A:
        ...:     pass
        ...:
    
    In [13]: A.__class__
    Out[13]: type
    
    In [14]: int.__class__
    Out[14]: type
    

    当你使用class关键字时,Python在幕后做的事情,就是通过元类来实现的。

    • 为什么重新定义__call__方法?

    提出该问题是因为,与Python类创建相关的方法是:

    __new__:类方法,负责对象的创建,在定义类时需要返回一个实例,在我们通过类名进行实例化对象时自动调用。
    __init__:初始化函数,负责对new实例化的对象进行初始化,负责对象状态的更新和属性的设置,在每一次实例化对象之后调用。

    而我们常用__call__方法只是为了声明这个类的对象是可调用的(callable)。

    但是,在metaclass中__call__方法还负责对象的创建,这就是为什么要重新定义的原因了。

    重定义了__call__方法之后,一个对象的创建过程大概如下图:

    我们验证一下:

    class TestMetaClass(type):
    
        def __init__(cls, what, bases=None, dict=None):
            print("metaclass init")
            super().__init__(what, bases, dict)
    
        def __call__(cls, *args, **kwargs):
            print("metaclass call")
            self = super(TestMetaClass, cls).__call__(*args, **kwargs)
            return self
    
    
    class TestClass(metaclass=TestMetaClass):
    
        def __init__(self, *args, **kwargs):
            print("class init")
            super().__init__()
    
        def __new__(cls, *args, **kwargs):
            print("class new")
            self = super().__new__(cls)
            return self
    
    a = TestClass()
    

    返回:

    metaclass init
    metaclass call
    class new
    class init
    

    可以看到,__call__方法在类执行__new____init__之前执行,这样就可以解释:

    在Singleton中的__call__方法对类属性__instance进行判断:

    1. 如果__instance为None,表明类还未进行实例化,那么给__instance赋值为元类的父类(type)的__call__方法。
    2. 如果__instance不为None,说明类已经进行过实例化,直接返回cls.__instance中的类实例。

    便实现了单例模式。

    除了重新定义__call__以外,元类可以通过实现__init__方法来定制实例,元类的__init__方法可以做到类装饰器能做到的任务事情,并且作用更大。

    如果想要进一步定制类,可以在元类中实现__new__方法。

    另,编写元类时,通常会把self参数改为cls,这样能更清楚的表明要构建的实例是类。

    元类的调用

    上述例子中,都是通过metaclass=''来设置类的元类,还可以这样:

    class TestClass():
        __metaclass__ = TestMetaClass
    
        def __init__(self, *args, **kwargs):
            print("class init")
            super().__init__()
    

    在执行类定义时,解释器会先寻找这个类属性中的__metaclass__,如果此属性存在,就将这个属性赋值给此类作为它的元类,如果此属性没有定义的话,就会向上查找父类的__metaclass__,如果没有发现任何的父类,并且解释器中也没有名字为__metaclass__的全局变量,这个类就是传统类,会使用type.ClassType作为此类的元类。

    以上。

  • 相关阅读:
    解决后退网页已过期或刷新询问是否重新提交表单的问题
    一行代码获取中文星期
    单例模式弹出窗体实现
    JAVA实现冒泡排序
    关于BufferedWriter.write超过30W条数据写入过慢问题。
    Ibatis的简单介绍
    链接注入(便于跨站请求伪造)(AppScan扫描结果)
    会话标识未更新(AppScan扫描结果)
    跨站点脚本编制实例(AppScan扫描结果)
    深入Java核心 Java内存分配原理精讲
  • 原文地址:https://www.cnblogs.com/ybjourney/p/14295872.html
Copyright © 2020-2023  润新知