本文转自:https://www.cnblogs.com/linhaifeng/articles/8029564.html
少量修改
目录
什么是元类
class关键字创建类的的流程分析
自定义元类控制类的创建
自定义元类控制类的调用
再看属性查找
什么是元类
一切源自于一句话:Python中一切皆为对象。让我们先来定义一个类,然后逐步分析。
class MyText(object):
work = '刺客'
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print('my name is {}'.format(self.name))
所有的对象都是实例化(调用类的过程称为类的实例化)得到的,比如对象 sample 是调用类 MyText 得到的。
sample = MyText('阿珍', 18)
print(type(sample)) # 查看对象sample的类是<class '__main__.MyText'>
如果一切皆为对象,那么类 MyText 的本质也是一个对象,既然所有对象都是调用类得到的,那么 MyText 必然也是调用了一个类得到的,这个类就称为元类。
于是我们可以推导出===> 产生 MyText 的过程一定发生 MyText = 元类(…)
print(type(MyText))
# 结果为<class 'type'>,证明是调用了type这个元类而产生了MyText,即默认的元类为type
class 关键字创建类的流程分析
上文我们基于 Python 中一切皆为对象的概念分析出:我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类(元类即是类的类,是女娲),内置的元类为 type。
class关键字再帮我们创建类时,必然帮我们调用了元类 MyText = type(...)
,那调用 type 时传入的参数是什么呢?必然是类的关键组成部分,而一个类有三大组成部分,分别是:
- 类名;
class_name = 'MyText'
- 类的基类元组;
class_bases = (object,)
- 类的命名空间;类的命名空间是执行类体代码而得到
调用 type 时会依次传入以上三个参数
综上,class关键字创建一个类的过程应该细分为以下四个步骤:
补充:exec() 函数的用法:
'''
exec(__object, __globals, __locals)
功能:执行字符串__object中的代码,并将产生的名字存放在局部命名空间中
参数:
__object:包含代码的字符串
__globals:全局作用域(字典形式, 需包含__object中用到的全局变量), 若不指定,则为 globals()
__locals:局部作用域(字典形式, 用于存放__object中产生的名字),若不指定,则为 locals()
'''
_globals = {'num': 10}
_locals = {}
string = '''
sun = num + 10
'''
exec(string, _globals, _locals)
print(_globals, _locals, sep='\n')
# {'num': 10, ...}
# {'sum': 20}
自定义元类控制类 MyText 的创建
一个类没有生命自己的元类,则默认它的元类为 type,除了使用内置元类,我们也可以通过继承 type 来自定义元类,然后使用 mateclass 关键字参数为一个类指定元类。
# 例如下
class Mymeta(type):
#只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
pass
class MyText(object,metaclass=Mymeta):
# 同于:MyText=Mymeta('OldboyTeacher',(object),{...})
work = '刺客'
def __init__(self,name,age):
self.name=name
self.age=age
def say(self):
print('my name is {}'.format(self.name))
自定义元类可以控制类的生产过程(就像类可以控制实例化对象的生产过程),类的生产过程其实就是元类的调用过程,即MyText = Mymeta('MyText', (boject,),{...})
;调用 Mymeta 会先产生一个空对象 MyText,然后连同调用 Mymeta 括号内的参数一同传给 Mymeta 下的 __init __ 方法,完成类的初始化,于是乎:
# 例如下
class MyMeta(type):
# 只有继承了type类的类才是一个元类,
def __init__(self, class_name, class_bases, class_dic):
# 以上四个参数都是自动传入
# print(self) # 可以看看这里会输出什么
# print('class_name:', class_name, '\n', 'class_bases:', class_bases, '\n', 'class_dic:', class_dic, sep='')
super().__init__(class_name, class_bases, class_dic)
# 调用父类的构造方法创建类
if class_name.islower():
raise TypeError('类名{}请修改为驼峰体'.format(class_name))
if '__doc__' not in class_dic or \
len(class_dic['__doc__'].strip(' \n')) == 0:
raise TypeError('类中必须有文档注释,并且文档注释不能为空')
class MyText(object, metaclass=MyMeta):
# 等价于 MyText = MyMeta('MyText', (object,), {'__doc__': 'This is a doc', name: '阿珍'...})
'''
This is a doc
'''
work = '刺客'
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print('my name is {}'.format(self.name))
自定义元类控制类的调用
众所周知,调用一个对象,就是触发对象所在类中的__call__方法(魔法方法__call__解析),如果把 MyText 也当做一个对象,那么在 MyText 这个和对象的类中也必然存在一个__call__方法。
# 例如下
class MyMeta(type):
# 只有继承了type类的类才是一个元类
def __call__(self, *args, **kwargs):
print(self, args, kwargs, sep='\n')
# <class '__main__.MyText'>
# ('阿珍', 18)
# {}
return '哈哈'
class MyText(object, metaclass=MyMeta):
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print('my name is {}'.format(self.name))
# 调用 MyText 就是在调用 MyMate 类中的__call__方法
# 然后将 MyText 传给 self ,溢出的位置参数传给*args,溢出的关键字参数传给**kwargs
# 调用 MyText 的返回值就是调用__call__的返回值
sample = MyText('阿珍', 18)
print(type(sample)) # <class 'str'>
默认的,调用 sample = MyText('阿珍', 18)
会做三件事:
- 产生一个空对象 sample
- 调用__init__方法初始化对象 sample
- 返回初始化好的sample
对应的,MyMeta 类中的__call__方法也用该做这三件事。
class MyMeta(type):
def __call__(self, *args, **kwargs): # self = <class '__main__.MyText'>
# 1、调用__new__产生一个空对象
obj = self.__new__(self) # 此处必须传参,代表创建一个 MyText 的对象 obj
# 2、调用__init__初始化空对象 obj
self.__init__(obj, *args, **kwargs)
# 3、返回初始化好的对象
return obj
class MyText(object, metaclass=MyMeta):
def __init__(self, name, age):
self.name = name
self.age = age
sample = MyText('阿珍', 18)
# 同于 sample = MyMeta.__call__(MyText, '阿珍', 18)
print(sample.__dict__)
# {'name': '阿珍', 'age': 18}
现在你知道为什么说实例化一个对象最先调用的是其类的__new__和__init__方法了吧!
上例的__call__相当于一个模板,我们可以在该基础上改写__call__的逻辑,从而控制调用 MyText 的过程,比如将 MyText 的对象的所有属性都变成私有的。
# 例如下
class MyMeta(type):
def __call__(self, *args, **kwargs): # self = <class '__main__.MyText'>
# 1、调用__new__产生一个空对象
obj = self.__new__(self) # 此处必须传参,代表创建一个 MyText 的对象 obj
# 2、调用__init__初始化空对象 obj
self.__init__(obj, *args, **kwargs)
# 利用字典生成式来使属性私有化
obj.__dict__ = {'_{}__{}'.format(self.__name__, k): v for k, v in obj.__dict__.items()}
# 3、返回初始化好的对象
return obj
class MyText(object, metaclass=MyMeta):
def __init__(self, name, age):
self.name = name
self.age = age
sample = MyText('阿珍', 18)
print(sample.__dict__)
# {'_MyText__name': '阿珍', '_MyText__age': 18}
再看属性查找
结合 python 继承实现原理 + 元类重新看属性的查找应该是什么样子呢?
在学习完元类后我们了解到,其实用 class 自定义的类都是元类的实例对象(包括 object 类本身也是元类 type 的一个实例,可以用 type(object) 查看)。我们学习过继承的实现原理,如果把类当成对象去看,那么可以将下述继承说成是:对象 MyText 继承对象 Foo,对象 Foo 继承对象 Bar,对象 Bar 继承对象 object。
# 例如下
class MyMeta(type):
n = 444
def __call__(self, *args, **kwargs):
obj = self.__new__(self)
self.__init__(obj, *args, **kwargs)
return obj
class Bar(object):
n = 333
class Foo(Bar):
n = 222
class MyText(Foo, metaclass=MyMeta):
n = 111
work = '刺客'
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print('my name is {}'.format(self.name))
print(MyText.n)
# 自下而上依次注释各个类中的 n=xxx,然后重新运行程序,发现 n 的查找顺序为 MyText->Foo->Bar->object->MyMeta->type
于是属性查找应该分为两层:一层是对象层(基于 C3 算法的 MRO)的查找,一层是类层(即元类层)的查找。
查找循序:
1、先对象层:MyText -> Foo -> Bar -> object
2、然后元类层:Mymeta -> type
依据上述总结,我们来分析一下元类 MyMeta 中__call__里的self.__new__属性的查找。
class MyMeta(type):
n = 444
def __call__(self, *args, **kwargs):
# self=<class '__main__.MyText'>
obj = self.__new__(self)
print(self.__new__ is object.__new__) # True
class Bar(object):
n = 333
# def __new__(cls, *args, **kwargs):
# print('Bar.__new__')
class Foo(Bar):
n = 222
# def __new__(cls, *args, **kwargs):
# print('Foo.__new__')
class MyText(Foo, metaclass=MyMeta):
n = 111
work = '刺客'
# def __new__(cls, *args, **kwargs):
# print('MyText.__new__')
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print('my name is {}'.format(self.name))
MyText('阿珍', 18)
# 触发 MyText 的类中的__call__方法的执行,进而执行self.__new__开始查找
总结:MyMeta 下的__call__里的 self.__new__在 MyText、Foo、Bar里都没找到__new__属性的情况下,回去找 object 里的__new __ ,也一定会在 object 中找到一个,根本不会,也没必要再去元类 MyMeta 或 type 中查找__new __。
我们在元类的__call__中也可以用object.__new __(self)去创建对象
但我们还是推荐在__call__中使用 self.__new __(self)去创建空对象,因为这种方式会检索三个类:MyText 、Foo、Bar,而object.__new __则是直接跳过了它们三个。
最后说明:
class MyMeta(type):
n = 444
def __new__(cls, *args, **kwargs):
obj = type.__new__(cls, *args, **kwargs)
# 必须按照这种传值方式
print(obj.__dict__)
# return obj # 只有在返回值是type的对象时,才会触发下面的__init__
return '123'
def __init__(self, class_name, class_bases, class_dic):
print('run。。。')
class MyText(object, metaclass=MyMeta):
n = 111
work = '刺客'
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print('My name is {}'.format(self.name))
print(type(MyMeta))
# 产生类MyText的过程就是在调用MyMeta,而MyMeta也是type类的一个对象,那么MyMeta之所以可以调用,一定是在元类type中有一个__call__方法
# 该方法中同样需要做至少三件事:
# class type:
# def __call__(self, *args, **kwargs): #self=<class '__main__.MyMeta'>
# obj=self.__new__(self,*args,**kwargs) # 产生MyMeta的一个对象
# self.__init__(obj,*args,**kwargs)
# return obj