学习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__ | 类的实例化对象在被调用时调用的函数 |
- __new__和__init__是类实例化过程自动调用的具体函数,两步完成之后,实例对象才算完整的产生,如果希望对类的实例化过程进行干预和定制化,都是基于这两个函数,包括接下来元类发生作用的原理,核心也在此。
- __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)
- class 关键字,声明了Demo类,并重载了__new__、__init__和__call__方法
- 当对以上代码运行时,实际上是创造了哥Demo类,且该类已经在内存中,且可以使用了(打印、继承、增加属性等),具体怎么创造Demo类的,下面会讲
- 当需要实例化Demo时,对Demo调用Demo()即可,此时会自动调用__new__和__call__方法
- 当希望实例化Demo产生的对象,也可被调用时,则需在类声明语句中重载__call__,当实例对象被调用时,自动执行该类声明语句内的__call__
- 以上几步最好深刻理解,因为下面讲解元类创造类时,会用到
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)
- type创造类的语法如上,其中classname是所要定义的类名 ,bases即继承的父类,元组格式,namespace_dict即命名空间,字典格式,定义了类内包含的属性和方法
- 在声明普通类时,如果没有指定元类,则默认的就是type元类
- 简单的例子如上代码内所示,比较简单,不再详解
- 从本质上看,其实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() #调用⑥
- 首先定义自己的元类Meta_Demo,其继承自type,一定要继承type
- 然后在元类内声明__new__、__init__和__call__,在元类声明语句运行时,其实是在实例化自定义的元类对象,此时会调用Python最原初的元类--type--的__new__和__call__,以上代码未写出,大家知道即可
- 下面声明类A,并使用metaclass指明类A使用的元类为Meta_Demo,这就意味着,当A声明语句执行的最后,会自动对元类Meta_Demo发起调用,即Meta_Demo()
- 对Meta__Demo调用时,会自动先发起对其元类(type)的__call__调用,因为Meta__Demo类是元类的实例化对象,而type的__call__会自动调用Meta_Demo的__new__和__init__
- 所以,如果运行了class A的声明语句,则会打印出包含①②的语句,此时Meta_Demo实例化完毕,产生其对象--类A
- 所以,如果希望对类A自动做一些处理,比如自动对其类属性和方法做检查和修改等,则可以在__new__和__init__内完成
- 以上,就是普通类--A,基于自定义元类Meta_Demo的实例化过程说明
- 接下来,对A实例化A()时,即对A发起调用
- 此时就会执行A的元类Meta_Demo里面的__call__
- 在这个函数里,会首先调用A声明的__new__,然后再调用__init__
- 这也就是为何实例化过程是先执行__new__再执行__init__的原因
- 至于类A实例化出a并对a进行调用,就不再展开了。
- 从上面可以看出,元类和普通类的实例化过程没任何区别,只不过元类实例化产生类,列实例化产生对象。
- 不管元类还是类
- 实例化时,均是调用其自身声明语句内的__new__和__init__
- 对其实例化后产生的对象发起调用时,均是执行其自身生命语句内的__call__
- 自定义元类也是对象,其继承自python的终极元类type,同时又是type类的实例化对象,所以对元类发起调用(即元类实例化产生普通类)时,本质是调用type类的__call__,由其调用自定义元类的__new__和__init__
到此,python类的终极底层逻辑,已经讲解和演示完毕,如果均已经理解了,那么python的整体语法就再没有难点了,恭喜已经具备的开发python框架的理论基础。
下面会举几个小例子,对元类的作用进行简单演示,但不限于此,掌握了元类才能真正发挥出Python动态语言的所有威力。
2.3.3 元类使用方式的总结
- 元类的指定方式:
- 在普通类声明语句内,头部基类括号内通过metaclass=,指定所用元类,则在类声明语句执行的最后,会自动调用该元类,并将该元类调用的返回值作为声明语句执行的最后结果
- 当然,也可将元类指定为任意一个可调用的对象,比如函数,但必须确保返回值是你想要的、已经自定义过的类
- 元类的具体使用原则:
- 如果要对类的创造过程进行干预,则自定义元类(必须继承type),并重载__new__和__init__
- 如果要对类的实例化过程进行干预,则自定义元类(必须继承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