python元类
python:一切皆对象
元类
如果把类也看做对象的话,那类这个对象的类就成为类的类,即元类。python中内置的默认元类是 type
我们用class关键字定义的所有的类以及内置的类都是由元类type实例化产生。
class机制
class是python的一个关键字,目的是用来创建类。它在底层实现类的定义本质上有四个步骤。
class People:
def __init__(self, name):
self.name = name
def talk(self):
print('hello')
# 类的三大特征:
- 类名:People
- 基类们:object,
- 名称空间:__dict__
第一步:获取类名:class_name = People
第二步:获取基类们:class_bases = (objects, )
第三步:获取类的名称空间:class_dict = {}
class_dict = {}
class_body = """
def __init__(self, name):
self.name = name
def talk(self):
print('hello')
"""
exec(class_body, {}, class_dict)
# class_dict = {'__init__': <function __init__ at 0x000002448A7B73A0>, 'talk': <function talk at 0x000002448A7B74C0>}
第四步:调用元类 type实例化产生People类这个对象
People = type(class_name, class_bases, class_dict)
上述四步是定义类的底层原理,也是我们定义产生一个类的第二种方式。
但是这种方式是复杂繁琐的,我们肯定会使用python给我们提供的class关键字来定义类。
自定义元类
上面class机制的四步中,我们可以发挥的地方在第四步,即用不同的元类实例化类这个对象,目的是为了按照需求控制类的定义和调用。
这就需要我们先定制出我们自己的元类,即自定义元类。
class People(metaclass=type): # class机制默认的元类是type:我们可以修改metaclass参数来选择自定义元类
def __init__(self, name):
self.name = name
def talk(self):
print('hello')
在python中type
是一切类的基石,所以我们自定义的元类也必须继承type
。
自定义的元类继承type
的目的是为了使用type
的大部分功能,我们只定制我们需要的那部分功能。
class Mymeta(type): # 只有继承了type的类才能作为元类使用
pass
class People(metaclass=Mymeta): # 使用Mytema元类,即Mymeta(class_name, class_bases, class_dict)
def __init__(self, name):
self.name = name
def talk(self):
print('hello')
实例化对象的本质
我们知道实例化一个对象会发生三件事,但其实我们确切知道的只有第二件事:初始化对象(__init__的功劳)。
现在,我们详细介绍介绍这三件事是如何发生的。其实这里只谈概念,具体是实现在本文下一节讨论。
class People:
def __init__(self, name, age)
self.name = name
jack = People('jack', 18)
# 实例化的三件事:
- 创建一个空对象
- 初始化空对象
- 返回初始化完成的对象
- 创建空对象由类的__new__()方法实现,__new__创建并返回一个空对象
- __init__接收这个空对象,并初始化该对象
- 最后返回初始化完成的对象。
整个三步流程是由类的类,即元类中的__call__方法管理的。因为实例化对象,本身是类的调用。
类本身又是一个对象,当它调用的时候就会触发其类的__call__函数的执行,即实例化对象时,触发元类的__call__函数。
在元类__call__内实现实例化的三件事。
当我们默认使用的是type元类时,想要自定制实例化过程中的需求是无法实现的。因为我们无法修改内置元类type的__call__方法。
当我们使用自定义元类时,就可以实现实例化过程需求的自定制。因为我么可以重写自定义元类的__call__方法,即自定制实例化需求。
自定义元类控制类的调用
控制类的调用就是控制对象的实例化过程。类也是一个对象,它的类是元类。类的调用就会触发元类的__call__函数执行
class Mymeta(type):
def __call__(self, *args, **kwargs): # self是People, *args, **kwargs接收类调用时括号内的实参
people_obj = self.__new__(self, *args, **kwargs) # 通过类调用类下面的方法,必须手动传参self
self.__init__(people_obj, *args, **kwargs) # 类调用手动传参people_obj 和 *args, **kwargs,还可以是 people_obj.__init__(*args, **kwargs) 此时是对象调用,自动传参
return people_obj
class People(metaclass=Mymeta):
def __init__(self, name, age):
self.name = name
self.age = age
def __new__(cls, *args, **kwargs): # 此处不写__new__,在元类的__call__中就会使用其父类的__new__
return super().__new__(cls) # 本质还是使用父类的的__nes__,没有继承则使用object的__new__
jack = People('jack', 18) # People.__call__('jack', 18)
print(jack.__dict__)
此处的元类Mymeta
是一个自定义元类模板,我们需要自定制对象实例化过程中的需求时可以在其中的__call__
里面的三步中添加需求功能。
自定义元类控制类的定义
自定义元类不仅可以控制定制实例化时的需求,还可以定制定义类时的需求。
从前面的 class机制我们知道,类定义过程的四步。我们可以通过自定义元类,实现控制class机制的第四步。
类的定义是通过元类的实例化产生的,因此类的定义过程必然牵涉到元类的__new__()、__init__()、以及元类的类的__call__()。此处,我们不讨论__call__(),原因是还没有搞清楚(太底层了)。
因此,我们通过自定义元类控制类的定义,就需要在自定义的元类中实现__new__()、__init__()两个方法。
class Mymeta(type):
def __init__(self, class_name, class_bases, class_dict):
if not class_name.istitle(): # 自定制需求
raise NameError('类名首字母必须大写')
if not self.__doc__:
raise TypeError('必须要有文档注释')
super().__init__(class_name, class_bases, class_dict) # 好像不重写父类的也没问题
def __new__(cls, *args, **kwargs):
class_obj = type.__new__(cls, *args, **kwargs)
# print(class_obj.__dict__)
return class_obj
# 元类的类.__call__(self, *args, **kwargs):
# 组织管理,元类.__new__、 __init__
# People = Mymeta(class_name, class_bases, class_dict),
class People(object, metaclass=Mymeta):
"""
deox
"""
x = 10
def f(self):
pass
print(People.__dict__)
属性查找
从对象出发的属性查找:对象 --> 对象所在的类 --> 父类 --> object
从类出发的属性查找:类 --> 父类 --> object --> 元类 --> type
最后的总结
# # type和object的关系
type是object的类,即object是由type实例化得到(type是所有有类的元类)
type是type自己的类
type又继承了object