type是内置的元类
用class关键字定义的所有的类都是由内置的元类type实现的
所有内置的类也都是由内置的元类type实现的
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
class Person:
def __init__(self, name: str, pid: int):
self.name = name
self.id = pid
def info(self):
print(f'{self.name}的身份码是{self.id}')
print(type(Person))
print(type(int))
运行结果
我们通过上述分析可以发现,class关键字其实是在调用type元类实例化了一个类并返回了一个类对象。而要知道在这个过程中发生了什么,首先我们需要明白类有哪些必须的属性
- 1、类名
- class_name = 'Person'
- 2、类的基类
- class_bases = (object,)
- 3、类的名称空间
- 内部包含了类的方法、属性构成的代码块
- class_dic = {}
下面我们根据类的三个要素,分别用class关键字和type元类实例化来分别创造一个Person类:
# 用class关键字创造Person类
class Person:
def __init__(self, name: str, pid: int):
self.name = name
self.id = pid
def info(self):
print(f'{self.name}的身份码是{self.id}')
print(Person)
运行结果
"""用type元类实例化一个Person类"""
class_name = 'Person'
class_bases = (object,)
class_dic = {
}
class_body = '''
def __init__(self, name: str, pid: int):
self.name = name
self.id = pid
def info(self):
print(f'{self.name}的身份码是{self.id}')
'''
# 用exec方法模拟创造类的名称空间
exec(class_body, {
}, class_dic)
print(type(class_name, class_bases, class_dic))
运行结果
可以看到,class关键字在创建一个类的时候做了三件事
从上面实验我们可以发现,元类实例化和class关键字所创造出来的类并没有太大区别,区别在于我们简单实例化元类创造出来的类并没有包含class关键字创造出来的类中的内置方法与功能属性。那元类存在的意义是什么呢?
刚刚我们明白了元类是如何创造一个类的,基于该过程中,我们发现在元类实例化的过程中需要传入三个参数,那么我们是否可以通过自定义一个元类然后给他传入三个参数来对类实现一个高度化的定制。
首先我们要清楚如何自定义一个元类,元类也是一个类,但是只有继承了type类的类才是元类。
根据这一点我们可以自定义一个元类Mymeta
class My_meta(type):
def __init__(self, class_name, class_bases, class_dic):
print('My_meta is running...')
print(self)
print(class_name)
print(class_bases)
print(class_dic)
接下来我们通过实例化这个元类My_meta来创造一个Person类
class Person(metaclass=My_meta):
def __init__(self, name: str, pid: int):
self.name = name
self.id = pid
def info(self):
print(f'{self.name}的身份码是{self.id}')
运行结果
My_meta is running...
Person
()
{'__module__': '__main__', '__qualname__': 'Person', '__init__': , 'info': }
我们知道在调用Mymeta的过程中会发生下面三件事:
上面过程中我们成功的自定义了一个简单的元类,并对它进行了实例化产生了一个Person类,那么如何通过元类对类进行一个控制,或者说是实现一个高度定制化呢?
元类要如何控制类呢? 从上面我们可以看到,元类实例化产生的类必然要遵循元类的逻辑,因此我们可以在元类的init方法中增加自定义的逻辑代码实现对实例化出来的类的控制
下面我们对类名进行一个控制,比如在python中,类名通常被要求首字母大写,但是不大写程序也不会报错,基于这点我们可以在元类中要求首字母必须大写,否则报错
class My_meta(type):
def __init__(self, class_name:str, class_bases, class_dic):
if not class_name.istitle():
raise NameError('类名首字母必须大写!')
class person(metaclass=My_meta):
pass
运行结果
NameError: 类名首字母必须大写!
在上面的操作中我们通过控制调用Mymeta过程中第二个过程,通过init方法对类进行了一个控制,那么我们是否可以通过控制第一个和第三个过程来实现对类的控制呢?
我们来分析第一个过程,我们知道创建一个空对象是利用了类的new方法进行实现的,所以我们只需要对元类中的new方法进行重写。
class My_meta(type):
# 第二部运行
def __init__(self, class_name, class_bases, class_dic):
print('init is running...')
print(self, class_name, class_bases, class_dic)
# 第一步运行
def __new__(cls, *args, **kwargs):
print('new is running...')
print(cls, args, kwargs)
return super().__new__(cls, *args, **kwargs)
运行结果
new is running...
('Person', (), {'__module__': '__main__', '__qualname__': 'Person', '__init__': , 'info': }) {}
init is running...
Person () {'__module__': '__main__', '__qualname__': 'Person', '__init__': , 'info': }
因此我们如果需要在初始化对象(类)对产生的类进行控制,那么我们在new方法中重写逻辑就可以达到我们的目的
下面我们再来分析第三个过程,返回一个对象。在我们调用Mymeta过程中,其实是在调用一个call方法,如果我们想让一个对象可以加括号进行调用,那么我们就需要在该对象的类中添加一个call方法,清楚了这个知识,我们就可以通过重写call方法对返回对象过程进行控制
class My_meta(type):
# 第二部运行
def __init__(self, class_name, class_bases, class_dic):
print('init is running...')
print(self, class_name, class_bases, class_dic)
# 第一步运行
def __new__(cls, *args, **kwargs):
print('new is running...')
print(cls, args, kwargs)
return super().__new__(cls, *args, **kwargs)
# 第三步运行
def __call__(self, *args, **kwargs):
return 'call is running...'
class Person(metaclass=My_meta):
pass
print(Person())
运行结果
new is running...
('Person', (), {'__module__': '__main__', '__qualname__': 'Person', '__init__': , 'info': }) {}
init is running...
Person () {'__module__': '__main__', '__qualname__': 'Person', '__init__': , 'info': }
call is running...
到这里,我们通过定制了一个元类并通过控制调用原来的三个流程来实现了控制类的产生。