• 面向对象之元类


    目录

    元类简介

    class关键字创建类的流程分析

    自定义元类

     _ _ new _ _ 方法

    _ _ call _ _方法

    call 方法的应用

    总结:类的产生与调用

    类的产生:

    People = Mymeta ( 参数 1,2,3)—>自定义元类加括号调用 Mymeta ( ) -----> 调用type元类. _ _ call _ _方法

    类的调用:


    元类简介

    python中所有的类的类型都是type,type就是元类:创建类的类叫做元类。

    1. """
    2. python中所有的类的类型都是type
    3. type就是元类:创建类的类叫做元类
    4. """
    5. a = '11'
    6. li = [11, 22]
    7. class MyTest:
    8. pass
    9. m = MyTest()
    10. print(type(a))
    11. print(type(li))
    12. print(type(m))
    13. print(type(MyTest))
    14. print(type(type))
    15. print(type(dict))
    16. print(type(str))
    17. print(type(TypeError))
    18. # 打印结果
    19. <class 'str'>
    20. <class 'list'>
    21. <class '__main__.MyTest'>
    22. <class 'type'>
    23. <class 'type'>
    24. <class 'type'>
    25. <class 'type'>
    26. <class 'type'>

    python中一切皆为对象。让我们先定义一个类,然后逐步分析

    1. class StanfordTeacher(object):
    2. school='Stanford'
    3. def __init__(self,name,age):
    4. self.name=name
    5. self.age=age
    6. def say(self):
    7. print('%s says welcome to the Stanford to learn Python' %self.name)

    所有的对象都是实例化或者说调用类而得到的(调用类的过程称为类的实例化),比如对象t1是调用类StanfordTeacher得到的

    1. t1=StanfordTeacher('lili',18)
    2. print(type(t1)) #查看对象t1的类是

    如果一切皆为对象,那么类StanfordTeacher本质也是一个对象,既然所有的对象都是调用类得到的

    那么StanfordTeacher必然也是调用了一个类得到的,这个类称为元类

    1. print(type(StanfordTeacher))
    2. # 结果为,证明是调用了type这个元类而产生的StanfordTeacher,即默认的元类为type

     

    class关键字创建类的流程分析


    用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类(元类可以简称为类的类),内置的元类为type

    class关键字在帮我们创建类时,必然帮我们调用了元类StanfordTeacher = type(参数 1,参数2,参数3),那调用type时传入的参数是什么呢?必然是类的关键组成部分,一个类有三大组成部分:
     

    参数1、类名 class_name=‘StanfordTeacher’

    参数2、基类 class_bases=(object,)

    参数3、类的名称空间 class_dic,类的名称空间是执行类体代码而得到的

    调用type时会依次传入以上三个参数—type( ‘StanfordTeacher’,(object,),{…} )

    综上,class关键字帮我们创建一个类应该细分为以下四个过程:

    1、拿到类名 'StanfordTeache

    2、拿到他的基类们;

    3、执行类体代码,拿到类的名称空间;

    4、调用元类返回对象(返回的对象就是我们需要的“类”) :StanfordTeache = type( class_name, class_bases, class_dict )

    自定义元类


    一个类没有声明自己的元类,默认他的元类就是type,除了使用内置元类type,我们也可以通过继承type来自定义元类,这样我们用 class 关键字生成的元类使用的就是我们自定义的元类

    class 类名( 继承的类名, … , metaclass = 自定义的元类名(默认为 type) ) :

    由 class 关键字创建一个类的四个步骤,只有最后一步调用元类生成类是无法修改的;比如:传入的类名必须首字母大写,不大写就报错, 为了实现这样的需求,元类显然是不能满足的

    需求:
    传入的类名必须首字母大写,不大写就报错;类中必须有文档注释,并且文档注释不能为空

    步骤分析:
    1、需求分析-----People类,满足类名首字母必须大写;

    2、首先需要自定义元类 Mymeta, 这样 class 类名(metaclass = Mymeta)就会对 类名首字母判断

    3、使用 class 关键字创建类,相当于 类名 = Mymeta ( ‘类名’ , (需要继承的基类们,) , { 类体代码执行后的名称空间 } )

    4、调用 Mymeta 类—发生"三件事":

    ​ 生成一个空对象,调用_ _ init _ _ 方法初始化对象,这个对象当做返回值

    5、由于 Mymeta 类传入了三个参数,所以需要在 _ _ init _ _ 方法,要传入这三个参数以及空对象

    6、在 _ _ init _ _ 方法中 对传入的参数进行 需求的自定义
     

    1. """ 自定义元类 """
    2. class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    3. def __init__(self,class_name,class_bases,class_dic):
    4. # self.class_name = class_name
    5. # self.class_bases = class_bases
    6. # self.class_dic = class_dic
    7. super(Mymeta, self).__init__(class_name, class_bases, class_dic) # 重用父类的功能
    8. if not class_name.istitle():
    9. raise TypeError('类名%s请修改为首字母大写' %class_name)
    10. if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' \n')) == 0:
    11. raise TypeError('类中必须有文档注释,并且文档注释不能为空')

     

     

     _ _ new _ _ 方法

    调用一个类会发生"三件事",其中第一件就是–产生一个空对象,那么这个空对象是怎么产生的呢?

    在调用类的时候,会首先自动触发 _ _ new _ _方法,执行里面的代码,在触发 init 方法

     

    所以,可以推断出 执行 init 方法之前,肯定已经有方法执行完毕了

    此时,我们重写 new 方法, 首先方法里什么都不写,直接执行,查看结果

    1. class Mymeta(type): # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    2. def __init__(self, class_name, class_bases, class_dic):
    3. print(self)
    4. print(class_bases)
    5. print(self.__bases__)
    6. def __new__(cls, *args, **kwargs):
    7. pass
    8. class People(metaclass=Mymeta):
    9. """注释"""
    10. def __init__(self, name, age):
    11. self.name = name
    12. self.age = age

     

    结果中 init 方法下的打印都没有执行,就直接结束了,而我们直到,new 方法是用来早一个空对象的,如果什么都没有返回,那么 init 方法也就不会调用,并且这个返回值也不能随便返回,必须要是造好的对象

    这个对象需要重用元类 type 的new 方法造出来,并且会自动继承 object 类,等等的一些内置的属性都会造好
     

    1. class Mymeta(type): # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    2. def __init__(self, class_name, class_bases, class_dic):
    3. print(self)
    4. print(class_bases)
    5. print(self.__bases__)
    6. def __new__(cls, *args, **kwargs):
    7. print('Mymeta--new')
    8. return super().__new__(cls, *args, **kwargs)
    9. class People(metaclass=Mymeta):
    10. """注释"""
    11. def __init__(self, name, age):
    12. self.name = name
    13. self.age = age

     

    那么,现在调用一个自定义类 Mymeta 的前两步:

    先调用 Mymeta 的 new 方法生成一个对象,这对象是通过 type 元类的 new 方法生成的,并且 new 方法最终返回了这个对象;

    然后再调用了 Mymeta 的 init 方法初始化这个对象 ( 一般情况下要重用 type 元类的 init 方法,以上的代码只为了突出 new 方法 )

    那么,就还剩下最后一步 – 返回这个对象。此时,需要思考一下:

    该由谁返回?是new 方法的返回值当做调用类的最终返回值,还是 init,亦或是别的方法?

    还有一个最底层的问题:为什么会先调用 new,在调用 init?是谁在控制这个流程的运行?

    _ _ call _ _方法

    调用类就是调用类的 _ _ call _ _方法,类调用后的运行逻辑,其实都是 call 方法来控制的

    call 方法的应用


    如果想让一个 对象 可以加括号 调用,那么需要在该对象的类中加入 call 方法,这个对象调用后的返回值就是 call 方法的返回值
     

     

     

    由此,我们可以解决调用自定义元类 Mymeta 的最后一步返回对象的问题。

    调用自定义元类 Mymeta 就是调用 type 元类的 call 方法,所以调用 Mymeta 的返回值就是使用 type 的 call 方法最终的返回值,并且这个 type 元类的 call 方法并不建议去自定义,在这里就不在过多介绍

    注意:并不是继承了父类就是调用父类,这里 Mymeta 与 type 元类是特例,区别调用类与继承类

    总结:类的产生与调用

    1、对象 ( ) -----> 调用 类. _ _ call _ _

    2、类 ( ) -----> 调用自定义元类. _ _ call _ _

    3、自定义元类 ( ) -----> 调用type元类. _ _ call _ _

    类的产生:


    People = Mymeta ( 参数 1,2,3)—>自定义元类加括号调用 Mymeta ( ) -----> 调用type元类. _ _ call _ _方法

    type元类. _ _ call _ _方法做了三件事:

    1、type元类. _ _ call _ _方法 调用自定义元类 Mymeta 内的 new 方法,造出一个空对象;

    2、type元类. _ _ call _ _方法 调用自定义元类 Mymeta 内的 init 方法,初始化这个对象;

    3、type元类. _ _ call _ _方法 会返回这个初始化好的对象;

    类的调用:


    obj = People(参数…)—> People 类加括号调用 -----> 调用自定义元类 Mymeta . _ _ call _ _方法

    自定义元类 Mymeta . _ _ call _ _方法做了三件事:

    1、 Mymeta . _ _ call _ _方法 调用自定义元类 People 类内的 new 方法,造出一个空对象;

    2、 Mymeta . _ _ call _ _方法 调用自定义元类People 类内的 init 方法,初始化这个对象;

    3、 Mymeta . _ _ call _ _方法 会返回这个初始化好的对象;
     

     

     生成一个对象的完整代码

    1. class Mymeta(type):
    2. def __new__(cls, *args, **kwargs):
    3. print('type 调用了 Mymeta 的 new 方法--生成一个空对象,即 People 类')
    4. "这里调用的是 type 的 new 方法,传入参数需要注意全部传会 type 元类"
    5. return super().__new__(cls, *args, **kwargs)
    6. def __init__(self, class_name, class_bases, class_dic):
    7. print('初始化这个对象--- People 类,给 People 类添加额外的功能')
    8. super(Mymeta, self).__init__(class_name, class_bases, class_dic) # 重用父类的功能
    9. # 自定义的类的功能
    10. if not class_name.istitle():
    11. raise TypeError('类名%s请修改为首字母大写' % class_name)
    12. if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' \n')) == 0:
    13. raise TypeError('类中必须有文档注释,并且文档注释不能为空')
    14. # 传入 Mymeta的参数:People, 以及传入People的参数
    15. def __call__(self, *args, **kwargs):
    16. """
    17. self---
    18. :param args: (1,)
    19. :param kwargs: {'y': 2}
    20. :return: 返回最终初始化好的代码
    21. """
    22. print('调用了 Mymeta 的 call 方法')
    23. # 调用 People 类里的 __new__方法,生成空对象
    24. People_obj = self.__new__(self, *args, **kwargs)
    25. # 调用 People 类里的 __init__方法,初始化空对象,注意:第一个传入的参数是生成好的空对象
    26. self.__init__(People_obj, *args, **kwargs)
    27. # 给 People 类生成的对象 obj 添加额外的功能
    28. print("给 People 类生成的对象 obj 添加额外的功能")
    29. People_obj.__dict__["新增一个属性"] = None
    30. # 返回初始化好的对象
    31. return People_obj
    32. class People(metaclass=Mymeta):
    33. """People 类的注释"""
    34. # 产生 People 类真正的对象
    35. def __new__(cls, *args, **kwargs):
    36. # 在这里就可以定制功能
    37. print('生成 People 类的空对象')
    38. print('传入的位置参数', args)
    39. print('传入的位置参数', kwargs)
    40. # 调用所继承的父类的__new__方法,这里就是 object 类,一定要传入 cls(当前这个类)
    41. "这里要区别于自定义元类的 new 方法,自定义元类调用的是 type 的 new 方法,传入参数是不一样的"
    42. return super().__new__(cls)
    43. def __init__(self, x, y=None):
    44. print("初始化 People 类的对象")
    45. self.x = x
    46. self.y = y
    47. print("初始化 People 类的对象结束")
    48. # 调用People 类生成对象---> People()= Mymeta.__call__()
    49. obj = People(1, y=2)
    50. print('最终的对象字典:', obj.__dict__)

     

  • 相关阅读:
    信创环境下使用80端口Nginx无法发送PUT和DELETE请求
    Unity 2D 游戏学习笔记(5)
    【云原生-K8s-1实例】通过yaml 文件编排一个web-MySQL小项目
    1194. 锦标赛优胜者
    【大禹DGC】1-基本介绍
    Go 命令
    Day25-线程简介
    RabbitMQ统一消息处理
    uniapp自定义顶部导航栏
    最长异或路径 ---- (字典树求异或最大)
  • 原文地址:https://blog.csdn.net/weixin_67531112/article/details/126816590