• Python 元类详解


    一、Type介绍

    在Python中一切皆对象,类它也是对象,而元类其实就是用来 创建类的对象 (由于一切皆对象,所以元类其实也是一个对象)。

    先来看这几个例子:

    例1:

    In [1]: type(12)
    Out[1]: int

    通过 type 可以查看对象的类型,也就是查看对象是那一类的,这里可以看出来 12 是 int 类型的也就是整数类。由于一切皆对象,那么我们也可以查看 int 是属于哪一个类型的。

    In [2]: type(int)
    Out[2]: type

    通过这样的操作可以看出来 int 是 type 类型的,也就是说 int 类是 type 类实例化得到的对象。按照这样的思路在来看看 type 类是什么类型。

    In [3]: type(type)
    Out[3]: type

    从上面可以看出来 type 是 type 类型的对象

    例2:在来看这样一个规律

    In [4]: class Foo:
       ...:     pass
       ...:
    
    In [5]: f = Foo()
    
    In [6]: type(f)
    Out[6]: __main__.Foo
    
    In [7]: type(Foo)
    Out[7]: type
    
    In [8]: type(type)
    Out[8]: type

    由上可以看出,实例 f 是 Foo 类的对象,Foo 这个自定义的类它是 type 类型的,并且 type 也是 type 类型的。

    总结:

    • 实例是类的实例;----------------------> f 是 Foo 类的实例
    • 类是 type 类的实例;-----------------> Foo 类是 type 类的实例
    • type 类是 type 类的实例;

    从上面的规律可以看出,所有的类终归都是由 type 实例化得来的,它就是最元始的,同时它也是一个类,所以也可以称之为 元类 (Metaclass)

    实例、类、元类之间的关系如图所示:

    二、Type 使用方法

    在 help(type) 显示的帮助文档中,可以看到这样两种使用方法;

    class type(object)
     |  type(object) -> the object's type
     |  type(name, bases, dict, **kwds) -> a new type

    第一种:直接带入对象,返回对象的类型,也是我们最常用的。

    第二种: type(name, bases, dict, **kwds) 返回一个新的类型,也就是一个新的类。

    • name:新类的名称;
    • bases:以元组的形式,声明父类;
    • dict:以字典的形式声明类的属性;

    实例1:用 type 创建一个名为 Foo1 的类

    In [10]: Foo1 = type("Foo1", (), {})
    
    In [11]: Foo1.__bases__
    Out[11]: (object,)

    虽然没有给 bases 赋值,但是 Python 中所有的类都是以 object 为基类,所以查看类 Foo1 的父类,会得到这样一个结果。PS: __bases__ 查看父类。

    也可以将 Foo1 实例化,就能看见 f 是 Foo1 类型,并且仿照之前的样子查找它的类型,可以一直查找到元类 type。

    In [12]: f = Foo1()
    
    In [13]: f.__class__
    Out[13]: __main__.Foo1
    
    In [14]: f.__class__.__class__
    Out[14]: type

    __class__ 是类的一个内置属性,表示类的类型,也是 类的实例 的属性,表示实例对象的类

    实例2:利用元类 type 所创建的类 Bar 继承了 Foo1 和 Book 两个类

    In [15]: class Book:
        ...:     def __new__(cls, *args, **kwargs):
        ...:         cls.website = 'www.cnblogs.com'
        ...:         return super().__new__(cls)
        ...:
    
    In [16]: Bar = type('Bar', (Foo1, Book), {'name': 'python'})
    
    In [17]: b = Bar()
    
    In [18]: b.name
    Out[18]: 'python'
    
    In [19]: b.website
    Out[19]: 'www.cnblogs.com'
    
    In [20]: b.__class__
    Out[20]: __main__.Bar
    
    In [21]: b.__class__.__class__
    Out[21]: type
        
    In [22]: Bar.__bases__
    Out[22]: (__main__.Foo1, __main__.Book)
    
    In [23]: Bar.__dict__
    Out[23]: mappingproxy({'name': 'python',
                  '__module__': '__main__',
                  '__doc__': None,
                  'website': 'www.cnblogs.com'})

    上述中 Bar 类除了在创建时设置的 name 外,还有从 Book 类继承来的 website,实例化后也可以读取到 name 和 website 两个属性的值,并且实例的类型是 Bar。

    三、Metaclass

    虽然使用第二种方法能定义出所需要的各种类型的类,但是,在实践中,这种形式并不常用,而是用这种形式;

    In [24]: class Meta(type):
        ...:     pass
        ...:
    
    In [25]: class Spam(metaclass=Meta):
        ...:     pass
        ...:
    
    In [26]: s = Spam()

    类 Meta 继承了 type ,也可以称为元类,而 Spam 类与之前定义的类的不同之处就在于,类名后括号内添加了参数 metaclass 来说明或指定元类,这样就利用了元类 Meta 创建了类 Spam。

    总结描述:

    • 类 Spam 是元类 Meta 的实例;
    • 元类 Meta 实例化得到了类 Spam;
    In [27]: s.__class__
    Out[27]: __main__.Spam
    
    In [28]: s.__class__.__class__
    Out[28]: __main__.Meta
    
    In [29]: s.__class__.__class__.__class__
    Out[29]: type

    下面做一个更详细的实例,来了解 Spam 与 Meta 之间的关系。

    class Meta(type):
    
        def __new__(cls, name, bases, attrs):
            print("执行了元类 Meta!")
            attrs['author'] = "xiaoyang-sir"
            spam_class = super().__new__(cls, name, bases, attrs)
            print(spam_class)
            return spam_class
    
    
    
    class Spam(metaclass=Meta):
    
        def __init__(self, website):
            self.website = website
    
            
    print(Spam.author)
    
    
    """
    Out:
        执行了元类 Meta!
        
        xiaoyang-sir
    
    """

    从上面程序运行的的结果可以直接看出当该段程序运行的时候,虽然并没有直接实例化 Spam 类,但是由于指定了 Meta 为 Spam 类的元类 ,所以 Meta 会直接实例化 Spam 类,并且元类 Meta 的构造方法 __new__() 中定义的属性 author 已经成为了实例 Spam 类的类属性。

    那么接下来将 Spam 实例化后,也就能得到想要的结果。

    s = Spam('www.cnblogs.com')
    print(s.website)
    print(s.author)
    
    """
    Out:
        执行了元类 Meta!
        
        www.cnblogs.com
        xiaoyang-sir
    """

    四、元类实现单例模式

    元类实现单例模式可以在元类中定义 __call__() 方法, __call__() 方法主要是在对象加括号被调用的时候执行 __call__() 方法中的内容。由于类是一个对象,所以在类加括号的时候就会执行元类中的 __call__() 方法。

    class Meta(type):
        __instance = None
    
        def __call__(cls, *args, **kwargs):
            if not cls.__instance:
                cls.__instance = type.__call__(cls, *args, **kwargs)
            return cls.__instance
    
    
    class Spam(metaclass=Meta):
        pass
    
    
    x = Spam()
    y = Spam()
    print(x)
    print(y)
    print(x==y)
    
    
    """
    Out:
        <__main__.Spam object at 0x00000251A51E7E50>
        <__main__.Spam object at 0x00000251A51E7E50>
        True
    """

     

  • 相关阅读:
    C++要笑着学:stack和queue
    Ubuntu 安装 npm 和 node
    RHCSA学习笔记(vi编辑器)
    计算机毕业设计springboot+vue基本微信小程序的健康管理系统
    Redis分布式锁相关总结
    计算机毕业设计源码——基于Android的真人社交游戏辅助应用开发
    太牛了,阿里这份Spring Cloud开发手册几乎涵盖了微服务的所有操作
    Spring5应用之AOP概念详解
    gitlab搭建2(linux搭建,外部windows访问,centos7)
    【数据结构】并查集
  • 原文地址:https://blog.csdn.net/m0_59485658/article/details/126022692