Python高阶知识之元类--彻底了解python底层类逻辑

本文探讨了Python中的一切皆对象概念,详细解析了类和对象的模型,包括__new__,__init__,和__call__等核心魔法函数的作用。文章进一步介绍了元类的概念,它是创造类的类,通过元类可以定制类的创建过程。通过实例展示了如何使用元类实现单例模式和类名首字母大写校验,强调了掌握元类对于理解Python底层逻辑的重要性。
摘要由CSDN通过智能技术生成

学习Python过程中,经常会遇到这样的说法,Python中,一切皆对象,包括内置数据类型、函数、类等等,int等数据类型、函数为对象还相对好理解,基本可以认为是内置类创建的实例对象,但类本身也是对象,就会稍微让人迷惑,本文主要就是深入分析Python的类和对象模型,彻底了解Python类的整体内在核心逻辑--元类,并详细解释类的创建、类的实例化等过程。

这些话题算是Python的高阶话题,是有一定的Python编程基础之后,再来学习或了解会比较合适,同时,可能看完之后,会感觉对实际的撸码没有啥太大帮助(只有框架的设计者才会关心这些工具和概念,但真正能设计框架的人少之又少),但笔者还是建议耐心研究、看完,这之后,会对Python的认知有一个实质性的变化,如果能彻底理解,那么你已经具备Python框架级产品开发的理论基础了!

一、Python类和对象模型概述

1.1 一切皆对象

在Python的语法世界里,所有的可创建或赋值给标识符的都是对象,都有其自身的属性和方法,也可以通过Python的内省机制(inspect)获取并访问,也有统一的传值逻辑,且可以对Python的所有内置数据类型进行个性化的功能拓展,在确保整体逻辑完美自洽的同时,也充分体现了Python语言的动态性和灵活性。

1.2 类和对象概述

在Python中,类和对象的基本概念和特性和其他OOP语言没有本质区别,类是对象的抽象和模型,对象是类的具体和实例,对象通过类的实例化过程创建,

1.3 类和对象相关核心魔法函数

与类和对象的实例化创建、调用等过程相关的,最核心的有3个魔法函数如下,先进行简单说明,因接下来讲解元类等会频繁提到。

魔法函数作用
__new__类用来实例化创建其对象第一步调用的函数,主要用于构造对象的名称、命令空间以及内存空间等,该函数必须返回类的实例化对象
__init__类用来对实例化之后的对象进行初始化的函数,主要对对象的实例属性等进行定义和初始化赋值,该函数不需要有返回值
__call__类的实例化对象在被调用时调用的函数
  1.  __new__和__init__是类实例化过程自动调用的具体函数,两步完成之后,实例对象才算完整的产生,如果希望对类的实例化过程进行干预和定制化,都是基于这两个函数,包括接下来元类发生作用的原理,核心也在此。
  2. __call__是在实例对象被调用(即后面加括号)自动调用的函数,该函数在普通类里基本较少重载使用(只有在写装饰器等的时候才会用到),这里介绍,主要是为了下面说明类实例化过程。

二、类和元类

2.1 元类

元类,指的是创造类的类,因为类也是对象,所以其必然是通过调用某个类创建的,这个类就是元类,在Python中,元类指的是type,但也可以通过继承创建自己的元类。

因为元类是创建类的类,所以可以通过元类对类的创造过程进行定制化,类似我们通过类对对象的创造过程进行定制化。

2.2 常规类的定义和实例化过程

常规类定义和实例化过程,核心是通过class关键字,以及对类调用来完成,我们看个示例代码

class Demo:
    def __new__(cls,*args,**kwargs):
        instance=super().__new__(cls,*args,**kwargs)
        return instance
    
    def __init__(self):
        self.name='dennis'
    
    def __call__(self):
        #Demo实例对象被调用时,执行
        print(self.name)
  1.  class 关键字,声明了Demo类,并重载了__new__、__init__和__call__方法
  2. 当对以上代码运行时,实际上是创造了哥Demo类,且该类已经在内存中,且可以使用了(打印、继承、增加属性等),具体怎么创造Demo类的,下面会讲
  3. 当需要实例化Demo时,对Demo调用Demo()即可,此时会自动调用__new__和__call__方法
  4. 当希望实例化Demo产生的对象,也可被调用时,则需在类声明语句中重载__call__,当实例对象被调用时,自动执行该类声明语句内的__call__
  5. 以上几步最好深刻理解,因为下面讲解元类创造类时,会用到

2.3 元类创造类的过程

2.3.1 简单说明--使用type

元类是用来创造类的类,即普通类是元类的实例化对象,Python内置的默认元类,是type。

平时我们使用type,一般是用来输出指定对象所属类名,比如type(int)等,但其实type也可以用于创建类,此时其就是元类,上代码

#type的语法
type(classname,bases,namespace_dict)

class Demo:
    name='dennis'
    
#下面和上面的classs声明语句作用相同
attrdict={'name':'dennis'}
Demo=type('Demo',(,),attrdict)
  1. type创造类的语法如上,其中classname是所要定义的类名 ,bases即继承的父类,元组格式,namespace_dict即命名空间,字典格式,定义了类内包含的属性和方法
  2. 在声明普通类时,如果没有指定元类,则默认的就是type元类
  3. 简单的例子如上代码内所示,比较简单,不再详解
  4. 从本质上看,其实Demo类就是type类的实例对象,而实例化过程,和普通类实例化对象过程没啥区别,所以如果我们继承type并重载__new__、__init__和__call__,就可以定义自己的元类并定制化类的创造和类的实例化过程了。

2.3.2 底层核心逻辑--终极奥义

直接上代码,然后对代码进行详细讲解

class Meta_Demo(type):
    def __new__(meta,classname,bases,attrdict):
        print('①Meta_Demo __new__,元类被调用以实例化普通类时,会执行')
        attrdict['name']='dennis'
        return super().__new__(meta,classname,bases,attrdict)
    
    def __init__(cls,classname,bases,attrdict):
        print('②Meta_Demo __init__,普通类被实例化后,对普通类(即元类的对象)初始化时,会执行')
        super().__init__(classname,bases,attrdict)
    
    def __call__(cls,*args,**kwargs):
        print('③Meta_Demo __call__,普通类被调用,即实例化时,会调用,该函数会自动调用普通类的__new__和__call__')
        #这里的self指的是Meta_Demo的实例化对象,也即类A
        obj=cls.__new__(cls,*args,**kwargs)
        cls.__init__(cls,*args,**kwargs)
        return obj
        #return super().__call__(*args,**kwargs) 

        
#声明A,并指定元类为Meta_Demo,运行会执行①②        
class A(metaclass=Meta_Demo): 
    def __new__(cls,*args,**kwargs):
        print('④A __new__,创建普通类实例的时候,会调用')
        instance_obj=super().__new__(cls,*args,**kwargs)
        #此处可以加自己的业务处理逻辑,以定制A实例化过程
        return instance_obj
    
    def __init__(self,*args,**kwargs):
        print('⑤A __init__ ,初始化普通类的实例对象时,会执行')
        #super().__init__(*args,**kwargs)
        
    def __call__(self):
        print('⑥A __call__ ,普通类的实力对象被调用时,会执行')

        
a=A() #调用③④⑤
a() #调用⑥
  1. 首先定义自己的元类Meta_Demo,其继承自type,一定要继承type
  2. 然后在元类内声明__new__、__init__和__call__,在元类声明语句运行时,其实是在实例化自定义的元类对象,此时会调用Python最原初的元类--type--的__new__和__call__,以上代码未写出,大家知道即可
  3. 下面声明类A,并使用metaclass指明类A使用的元类为Meta_Demo,这就意味着,当A声明语句执行的最后,会自动对元类Meta_Demo发起调用,即Meta_Demo()
  4. 对Meta__Demo调用时,会自动先发起对其元类(type)的__call__调用,因为Meta__Demo类是元类的实例化对象,而type的__call__会自动调用Meta_Demo的__new__和__init__
  5. 所以,如果运行了class A的声明语句,则会打印出包含①②的语句,此时Meta_Demo实例化完毕,产生其对象--类A
    1. 所以,如果希望对类A自动做一些处理,比如自动对其类属性和方法做检查和修改等,则可以在__new__和__init__内完成
  6. 以上,就是普通类--A,基于自定义元类Meta_Demo的实例化过程说明
  7. 接下来,对A实例化A()时,即对A发起调用
    1. 此时就会执行A的元类Meta_Demo里面的__call__
    2. 在这个函数里,会首先调用A声明的__new__,然后再调用__init__
    3. 这也就是为何实例化过程是先执行__new__再执行__init__的原因
  8. 至于类A实例化出a并对a进行调用,就不再展开了。
  9. 从上面可以看出,元类和普通类的实例化过程没任何区别,只不过元类实例化产生类,列实例化产生对象。
  10. 不管元类还是类
    1. 实例化时,均是调用其自身声明语句内的__new__和__init__
    2. 对其实例化后产生的对象发起调用时,均是执行其自身生命语句内的__call__
    3. 自定义元类也是对象,其继承自python的终极元类type,同时又是type类的实例化对象,所以对元类发起调用(即元类实例化产生普通类)时,本质是调用type类的__call__,由其调用自定义元类的__new__和__init__

到此,python类的终极底层逻辑,已经讲解和演示完毕,如果均已经理解了,那么python的整体语法就再没有难点了,恭喜已经具备的开发python框架的理论基础。

下面会举几个小例子,对元类的作用进行简单演示,但不限于此,掌握了元类才能真正发挥出Python动态语言的所有威力。

2.3.3 元类使用方式的总结

  1. 元类的指定方式:
    1. 在普通类声明语句内,头部基类括号内通过metaclass=,指定所用元类,则在类声明语句执行的最后,会自动调用该元类,并将该元类调用的返回值作为声明语句执行的最后结果
    2. 当然,也可将元类指定为任意一个可调用的对象,比如函数,但必须确保返回值是你想要的、已经自定义过的类
  2. 元类的具体使用原则:
    1. 如果要对类的创造过程进行干预,则自定义元类(必须继承type),并重载__new__和__init__
    2. 如果要对类的实例化过程进行干预,则自定义元类(必须继承type),并重载__call__

2.4 元类的应用举例

2.4.1 使用元类实现单例逻辑封装

单例模式,即某类只会产生一个实例对象,不管对该类实例化多少次。

如果使用元类,核心的思路就在于如何干预普通类的实例化过程,直接想到的就是结合元类的__call__

#如果是使用继承的方式,则
class Single_Instance:
    __instance=''
    def __new__(cls,*args,**kwargs):
        if cls.__instance == '':
           cls. __instance= super().__new__(cls,*args,**kwargs)
        return cls.__instance
class A(Single_Instance):
    pass

#如果使用元类,则
class Meta_Single_Instance(type):
    #在创造类的时候,在其命名空间加一个__instance类属性
    def __new__(meta,classname,bases,attrdict):
        attrdict['_Meta_Single_Instance__instance']=''
        return super().__new__(meta,classname,bases,attrdict)
    
    def __call__(cls,*args,**kwargs):
        if cls.__instance == '':
            cls.__instance=type.__call__(cls,*args,**kwargs)
        return cls.__instance
    
class Single_Instance(metaclass=Meta_Single_Instance):
    pass

#或,如果使用元类,则
class Meta_Single_Instance(type):
    #在创造类的时候,在其命名空间加一个__instance类属性
    __instance=''
    
    def __call__(cls,*args,**kwargs):
        if cls.__instance == '':
            cls.__instance=type.__call__(cls,*args,**kwargs)
        return cls.__instance
    
class Single_Instance(metaclass=Meta_Single_Instance):
    pass

2.4.2 使用元类强制对类名进行大写校验

以下代码会检查类名首字母是否大写,如果不是,则会报错语法错误,并提示 类首字母必须大写。

class Meta_Capname(type):
    #在创造类的时候,在其命名空间加一个__instance类属性
    def __new__(meta,classname,bases,attrdict):
        if not classname.istitle():
            raise SyntaxError('类首字母必须大写')
        return super().__new__(meta,classname,bases,attrdict)

class a(metaclass=Meta_Capname):
    pass    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值