一、前言:
要搞懂元类必须要搞清楚下面几件事:
-
类创建的时候,内部过程是什么样的,也就是我们定义类class 类名()的过程底层都干了些啥
-
类的调用即类的实例化过程的了解与分析
-
我们已经知道元类存在的情况下的属性查找新顺序分析
1、先来搞清楚我们创建一个类的过程:
class Newclass(): # class定义一个类,类名为:Newclass # 下面的代码都是类体代码,也就是类的属性们 def __init__(self, name, age): self.name = name self.age = age coutry = 'China' def task(self): print('%s is sleeping' % self.name)
当我们用class声明要创建一个类的时候,实际上内部流程是这样的(以上面的为例):
①.定义类名Newclass:class_name = 'Newcalss'
②.设定这个类Newclass的父类(基类)们(一个类可以继承多个类):class_bases = (object,) 不设定默认继承object
③.执行类体代码,拿到类的名称空间:class_dic = {...} (这里的字典就是我们前面学习类的时候查看类里面的属性方法.__dict__的内容。
2、我们调用创建的类Newclass(也就是实例化对象)的过程:
t1 = Newclass('sgt', 30) # 调用类,实例化t1这个对象
通过调用类Newclass实例化出对象t1时,会发生以下三件事:
①.先产生一个空的对象obj
②.调用__init__方法,对对象obj进行初始化,将默认属性丢进空对象对应的名称空间中
③.将初始化的对象obj返回,t1,就是返回结果的接受者,也就是说t1就是实例化出来的一个对象
二、开始认识元类
1、类产生的过程分析:
还是先定义一个类来分析分析:
class Newclass(): def __init__(self, name, age): self.name = name self.age = age coutry = 'China' def task(self): print('%s is sleeping' % self.name)
t1 = Newclass('sgt', 30)
首先,所有的对象都是实例化或者说调用类而得到的(调用类的过程称之为实例化对象),比如对象t1是调用类Newclass得到的
如果一切皆对象,那么Newclass本质也可以看成一个对象,既然所有的对象都是调用类得到的,那么是不是可以大胆的想象我们声明class创建一个类Newclass的时候,是否也是调用一个更高级的类得到的呢?
事实就是我们想的那样,这个‘实例化类的类’就是我们今需要好好了解的‘元类’。
于是我们可以大致有这么过程:产生Newclass的过程一定发生了:Newclass = 元类(...)
# 我们来分别打印一下实例化出的对象t1和创建的类Newclass的类型: print(type(t1)) print(type(Newclass)) # 结果是: # <class '__main__.Newclass'> # <class 'type'> # 我们可以推出: # t1是调用Newclass产生的 # Newclass是调用type产生的
一开始我们就提前了解了class出一个类Newclass时候发生的过程,这里再补充一下,如果我们不用class创建类的另外一种原始方法:
# 引入函数exec(object, globals, locals) exec用法: object:类体代码,包含一系列python代码的字符串 globals:全局作用域(字典形式),如果不指定,默认为globals() locals:局部名称空间(类的名称空间) 可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中 # 不依赖class关键字创建一个自定义类 类名class_name= 'Newcalss' 继承的类们:class_bases = (object,) 类体代码:class_body = ''' def __init__(self, name, age): self.name = name self.age = age coutry = 'China' def task(self): print('%s is sleeping' % self.name) ''' exec(class_body,{},class_dic) # 创建一个名称空间,将类体代码放入class_dict中,这个class_dict就代表exec创建的名称空间 # 调用type得到自定义的类: Newclass = type(class_name, class_bases, class_dict)
2、自定义元类,控制类Newclass的创建过程
既然知道了类的创建过程,那么我们就可以自定义元类来控制类的创建
首先:一个元类如果没有声明自己的元类,默认它的元类就是type,出了使用内置元类type,我们也可以通过集成type来自定义元类,然后使用metaclass关键字参数为一个类指定元类
class MyMeta(type): # 只有继承了type的类才能称之为一个元类,否则就是普通的类 pass class Newclass(object,metaclass=MyMeta): # 继承基类object,为Newclass指定元类为MyMeta def __init__(self, name, age): self.name = name self.age = age coutry = 'China' def task(self): print('%s is sleeping' % self.name)
然后:自定义元类可以控制类的产生过程,类的产生过程其实就是元类的调用过程,即:
Newclass = Mymeta(‘Newclass’,(object,),{.....}),
一开始我们先预习了,调用类是发生的过程:同理调用MyMeta会先产生一个空对象Newclass,然后连同MyMeta括号内的参数一同传给MyMeta下的__init__方法,完成初始化,所以我们可以在这个过程中做一下事情:
class MyMeta(type): # 只有继承了type的类才能称之为一个元类,否则就是普通的类 def __init__(self, class_name, class_bases, class_dict): super().__init__(class_name, class_bases, class_dict) if class_name.islower(): # 给类名做限制,必须为驼峰体,否则抛异常 raise TypeError('类名必须为驼峰体') # 给类中注释的存在性加以限制,必须要有注释且不能为空,否则抛异常 if '__doc__' not in class_dict or len(class_dict['__doc__'].strip(' \n')) == 0: raise TypeError('类必须要求有文档注释,且不能为空') class Newclass(object,metaclass=MyMeta): # 继承基类object,为Newclass指定元类为MyMeta ''' 这是Newclass类的注释 ''' def __init__(self, name, age): self.name = name self.age = age coutry = 'China' def task(self): print('%s is sleeping' % self.name)
3、自定义元类,控制类Newclass的调用过程
一开始提过:调用类就是实例化对象,那么调用这个行为,就必须要知道__call__这个知识点,所以先来说说__call__:
class Newclass(): coutry = 'China' def task(self): print('%s is sleeping' % self.name) def __call__(self, *args, **kwargs): print('__call__被调用了>>>', self) print('__call__被调用了>>>', args) print('__call__被调用了>>>', kwargs) t1 = Newclass() ## 要想让t1这个对象可调用,需要在该对象的类中定义一个__call__方法, # 该方法会在对象t1在调用时候自动触发 ## 调用t1的返回值就是__call__方法的返回值 res = t1('a', 'b', 8, name = 'jason', age = 18) # 右键运行结果: __call__被调用了>>> <__main__.Newclass object at 0x000001AD69359828> __call__被调用了>>> ('a', 'b', 8) __call__被调用了>>> {'name': 'jason', 'age': 18}
由上面的例子得知,调用一个对象,就会触发对象所在类中的__call__方法的执行,所以我们在调用类Newclass(这里将类也可以看成对象)实例化对象时候,也应该在类Newclass的元类中必然存在一个__call__方法。
class MyMeta(type): def __init__(self, class_name, class_bases, class_dict): super().__init__(class_name, class_bases, class_dict) def __call__(self, *args, **kwargs): print(self) # <class '__main__.Newclass'> print(args) # ('sgt', 18) print(kwargs) # {} return 123 class Newclass(object,metaclass=MyMeta): def __init__(self, name, age): self.name = name self.age = age coutry = 'China' def task(self): print('%s is sleeping' % self.name) t1 = Newclass('sgt', 18) print(t1) # 123
通过上面例子可以总结出:
- 调用Newclass就是在调用Newclass类中的__call__方法,(Newclass类中没有按照属性查找去基类中找)
- 触发__call__方法后会将Newclass传给self,溢出的位置参数传给*,溢出的关键字参数传给**
- 调用Newclass的返回值就是触发__call__方法函数的返回值,这里通过打印t1得出结果123可以得出。
好了,我们在来回顾下,实例化对象t1的过程:
- 先产生一个空的对象obj
- 调用__init__方法,对对象obj进行初始化,将默认属性丢进空对象对应的名称空间中
- 将初始化的对象obj返回,t1,就是返回结果的接受者,也就是说t1就是实例化出来的一个对象
所以,对应的Newclass在实例化出对象t1时候也应该做上面三件事:
class MyMeta(type): def __init__(self, class_name, class_bases, class_dict): super().__init__(class_name, class_bases, class_dict) def __call__(self, *args, **kwargs): # 1 调用__new__产生一个空对象obj: obj = self.__new__(self) # __new__会产生空对象的名称空间 # 这里self.__new__是通过self(类Newclass)来调用__new__方法,通过属性查找默认在基类object中 # 括号里self的意思是创建类Newclass的对象的名称空间。 # 2 调用__init__初始化空对象obj self.__init__(obj, *args, **kwargs) # 注意这里的第一个参数是obj,因为我们初始化的是我么创建的空对象 # 3 返回初始化好的对象obj return obj class Newclass(object,metaclass=MyMeta): def __init__(self, name, age): self.name = name self.age = age coutry = 'China' def task(self): print('%s is sleeping' % self.name) t1 = Newclass('sgt', 18) # 实例化对象 print(t1.__dict__) # {'name': 'sgt', 'age': 18} # 查看实例化对象的结果
上面就是我么通过调用类,实例化对象的过程元类中的__call__做的事情,既然知道了这个过程,我么也能自定义私人的元类,那么我们就可以从基础上改写__call__方法来控制Newclass类调用的过程,比如将Newclass类实例化的对象的属性变成我们想要的结果。
class MyMeta(type): def __init__(self, class_name, class_bases, class_dict): super().__init__(class_name, class_bases, class_dict) def __call__(self, *args, **kwargs): obj = self.__new__(self) self.__init__(obj, *args, **kwargs) # 在初始化完的对象返回之前进行修改 res = {k.upper(): v for k, v in obj.__dict__.items()} # 将对象的属性名大写 return res class Newclass(object,metaclass=MyMeta): def __init__(self, name, age): self.name = name self.age = age coutry = 'China' def task(self): print('%s is sleeping' % self.name) t1 = Newclass('sgt', 18) print(t1) # {'NAME': 'sgt', 'AGE': 18}
4、在知道元类存在的情况下的属性查找顺序