下面进入到了 ORM 的学习,ORM 篇幅较长,因为函数太多。我们主要分为两个部分,Model 和 QuerySet
元类
在python中万物皆对象,类也是一个对象,自定义的类或是python中内置的类都是由元类(type)实例化来的。因此元类也是一种类。
元类 ——实例化——> 类 ——实例化——> 对象
元类实例化类
使用元类实例化类需要三个参数:类名,基类(即继承于哪些父类),类名称空间(由类子代码执行得到)
# type是内置元类,调用type可实例化一个自定义的类
type(class_name, class_bases, class_dict)
原类实例化流程(与类实例化对象的过程一致):
- 调用__new__方法产生一个空对象(类)
- 调用__init__方法初始化对象(类)
- 返回初始化后的对象(类)
__new__
__new__方法会在类实例化过程中首先被调用,生成并返回一个空对象
class MyType(type): # 继承type
def __new__(cls, *args, **kwargs):
print("MyType.__new__")
return super().__new__(cls, *args, **kwargs) # 使用父类即type生成空对象
__init__
__init__用于初始化空对象,在自定义元类中,通常会传入四个参数:self(即实例化的对象本身),类名,基类,类名称空间。后三个参数是调用自定义元类实例化类所需的三个参数,可在此构造函数中对类名,基类,类名称空间做一些限制或后处理。
def __init__(self, class_name, class_bases, class_dict):
if '_' in class_name:
raise NameError("类名不能有下划线!") # 限制类名中不能出现下划线
if not class_dict.get('__doc__'):
raise SyntaxError('定义类必须写注释!') # 限制类中必须有注释文档,如有注释文档,类名称空间中有'__doc__'键
__call__
类可以被调用来实例化一个对象,因此自定义元类必须要有__call__方法,它在对象被调用时触发执行。
# 对象可以被调用执行,产生这个对象的类必须有__call__方法
class Dog(metaclass=MyType):
def __init__(self, name, color):
self.name = name
self.color = color
def info(self):
print('name: {}, color: {}'.format(self.name, self.color))
def __call__(self, *args, **kwargs):
print("Dog.__call__")
return "wang"
def __new__(cls, *args, **kwargs):
obj = super().__new__(cls) # 使用父类,即object中的__new__方法来生成一个空对象
return obj
dog1 = Dog('ha', 'white')
print(dog1()) # Dog.__call__, wang, 在调用对象时触发类的__call__方法
同理,Dog这个类可以被调用实例化对象,说明自定义元类中也有__call__方法。
别忘了,类实例化为对象的三个步骤:生成空对象,初始化对象,返回初始化后的对象,即这些过程被封装在元类中的__call__方法中。
def __call__(self, *args, **kwargs):
dog_obj = self.__new__(self) # 调用Dog中的__new__方法产生一个空对象
self.__init__(dog_obj, *args, **kwargs) # 调用Dog中的__init__方法来初始化对象,因此第一个参数时__new__出来的空对象
# 修改对象的属性名
new_dic = {}
for k in dog_obj.__dict__:chu
new_dic[f'New_{k}'] = dog_obj.__dict__[k]
dog_obj.__dict__ = new_dic
return dog_obj # 返回对象
dog1 = Dog('ha', 'white')
print(dog1.__dict__) # {'New_name': 'ha', 'New_color': 'white'}
完整代码
自定义元类流程梳理:
- 继承type类
- 自定义元类本质上也是一个类,实例化生成一个类,因此会先调用__new__方法产生空对象
- 再调用__init__方法初始化空对象
- 使用自定义元类实例化出来的类可被调用,因此自定义元类中有__call__方法
- 实例化出来的类也可实例化为一个对象,因此__call__方法中包含生成空对象,初始化空对象并返回这三个步骤。
# 产生一个dog对象需要调用Dog,即Dog('ha', 'white'),会触发自定义元类MyType中的__call__方法
class MyType(type): # 继承type
def __init__(self, class_name, class_bases, class_dict):
if '_' in class_name:
raise NameError("类名不能有下划线!")
if not class_dict.get('__doc__'):
raise SyntaxError('定义类必须写注释!')
# print(self.__bases__)
def __new__(cls, *args, **kwargs):
print("MyType.__new__")
return super().__new__(cls, *args, **kwargs)
def __call__(self, *args, **kwargs):
dog_obj = self.__new__(self) # 调用Dog中的__new__方法产生一个空对象
self.__init__(dog_obj, *args, **kwargs) # 调用Dog中的__init__方法来初始化对象,因此第一个参数时__new__出来的空对象
# 修改对象的属性名
new_dic = {}
for k in dog_obj.__dict__:
new_dic[f'New_{k}'] = dog_obj.__dict__[k]
dog_obj.__dict__ = new_dic
return dog_obj
class Dog(metaclass=MyType):
"""
test
"""
def __init__(self, name, color):
self.name = name
self.color = color
def info(self):
print('name: {}, color: {}'.format(self.name, self.color))
def __call__(self, *args, **kwargs):
print("Dog.__call__")
return "wang"
def __new__(cls, *args, **kwargs):
obj = super().__new__(cls) # 使用父类,即object中的__new__方法来生成一个空对象
return obj
if __name__ == '__main__':
dog1 = Dog('ha', 'white') # MyType.__new__
print(dog1()) # Dog.__call__, wang, 在调用对象时触发类的__call__方法
print(dog1.__dict__) # {'New_name': 'ha', 'New_color': 'white'}
Model
这时 Model 的源码。我们编写 Model 类对应数据库中的表的时候,都必须继承这个类,可以看到这个类没有继承任何类,但是它使用了类构造器,也就是元类 ModelBase
ModelBase
我们在 ModelBase.__new__
方法内第一行打上断点。然后开始 DEBUG 调试。
需要注意的是,本文最上面已经描述了元类,为什么这么做呢?我们自定义类 Model 类需要继承 Django 的 Model 类,那么 Django 的 Model 类必然需要先加载,而 Django 的 Model 类又是由 ModelBase 像构造一个对象一样构造出来了,所以如果解释器加载了 Django 的 Model 类,那么 ModelBase 类肯定已经构造过 Django 的 Model 类,并且 Django 的 Model 类一定是在整个 Django 中所有的自定义的 Model 类之前构造出来的,不然自定义的 Model 类继承谁呢。
【1】表示当前的类是 ModelBase,我们自定义一个类,类方法中的 cls 也是指向了当前的自定义类,没毛病。
【2】指向了当前正在构造的类对象,正如我们所分析的那样,第一个被 ModelBase 构造的正是 Model。
需要注意 attrs。
attrs 的内容正是 Django 的 Model 类中定义的那些内容,也就是说,元类的 __new__
方法中的所有参数都是 Python 解释器自己获取并传递的,不需要我们自己生成并传递。
另外需要注意的是,所有的 Model 类,包括自定义的,都是在 Django 环境加载的时候执行的,我们将上面的调试结果中的调试器从下往上浏览,就会发现,并没有什么专门的加载函数来加载这些 Model 类,因为在Django加载某些东西的时候,引用到了 Model 类,因此出发了我们的断点
【3】这个是调试按钮,点击这个按钮可以跳出当前的函数,返回到上一个函数的调用出,我们需要不断点击【3】,直到我们的自定义 Model 类出发了 ModelBase 这个元类,我们再来详细分析一下 ModelBase.__new__
ModelBase.__new__
首先调试到一个创建自定义类这里。
【1】当前正在创建的类名
【2】当前类继承的类。如果某个类继承的类使用了元类来构造自己,那么这个元类对这个类的所有子类同样生效
【3】此时的 attrs 是在我这个自定义类 Supplier 中自己定义的,也就是说在元类的 __new__
方法里,attrs 始终指向的是当前想要构造的类中定义的类属性,不包括他继承的类的属性。
【4】一行一行的调试和执行。
【1】获取元类的创建函数,也就是 type.__new__
type 是一个Python 内置类,用来创建类的最基础的元类,所有的元类都需要继承 type
【2】遍历当前要构造的类的所有父类,是一个列表,将这个列表中的 ModelBase 子类对象提取出来,基本上我们所有的自定义 Model 类的父类都不会继承 ModelBase 及其子类。所以这里 parents 是一个空列表
【3】提取当前要构造的类所属的模块儿。
【4】构造了一个新属性字典,并将新提取的 __moudle__
放入这个新的属性字典中
【5】提取自定义 Model 中的 Meta 类,并将其从 attrs 参数字典中剔除。如果我们想要自定义表名等操作,就需要在每一个 Model 类中再自定义一个 Meta 子类。
【6】将 attrs 中剩下的元素进行分类存储,主要是将普通类属性和数据库表类属性给分开,所有的表字段类都继承自 Field 类,这个类有一个方法,名为 contribute_to_class,所以,如果某个类属性存在这个元素,他就是数据库表类的子类。
【7】用除数据库表类属性之外的属性构造一个新类出来,这些类属性基本上都是类的描述。比如 __doc__
, __module__
等
【8】获取当前要构造的类是否为抽象类,抽象类没有对应的具体的表,是不能进行 orm 计算的,也不能生成表。
【9】尝试从当前要构造类中提取 Meta。前面已经从 attrs 中尝试提取了 Meta,那么此次只能从从父类中提取 Meta。
【10】尝试从当前类中提取 _meta
属性,这个在我这里是提取不到的。但其实看了后面的代码就会知道,这个属性跟 Meta 有关的。
【1】做了一个校验,如果当前要构造的类中的 Meta 这个属性没有 app_label,又找不到这个类所属的应用,又不是抽象类,就报错
【2】这个需要着重介绍一下,一个普通的类构造的实例,这个实例是可以调用类方法的,同样类是元类的实例,所以类可以调用元类的方法。于是 new_class 可以调用 add_to_class 方法。这个方法尝试向当前类中添加一个属性,或者一个该类中某个数据库字段类添加一个属性。具体的作用后续再说。关键是本次调用是向当前自定义类添加一个属性 _meta
, 他是一个 Options 实例,这个实例是对自定义 Model 类的更详细的描述,里面对每一种属性多出了默认值处理
【3】添加异常描述,暂且不理
下面的代码仍然是 ModelBase.__new__,但是挑重点说,毕竟我也是刚刚自研源码,不能钻研每一个细节
这里需要详细解释一下,需要注意的是每一个窗口下方指明了该段代码所属的文件
【1】这里尝试将自定义 Model 类的每一个数据库字段属性写进 new_class, 于是触发了【2】
【2】这里会进行一下甄别,如果是一般的类属性直接就写进了 new_class, 如果是 Field 类的子类,那么就需要调用该类的 contribute_to_class,注意这里是 value 其实是一个 Field 类的子类的实例,contribute_to_class 方法其实是 Field 类的方法。这里的传递的 cls 其实是 new_class, 还有当前准备操作的属性的名字。触发了【3】
【3】cls._meta 就是上面新创建的 Options 的实例,于是触发了【4】
【4】这里依据三种判定,将数据库字段对象分门别类写进三个列表,其中一般的数据库字段对象就写进了 local_fields 数组中,并且经过了排序。add_field 字段属于 Options
如果是抽象类,直接就返回了,如果非抽象类,就执行了 _prepare
方法
如果自定义的 Model 中没有 objects,就会默认写一个。
Model.objects
objects 其实是一个 Manager 的实例。
对于自定义 Model 有这样一个特性,如果我们没有给他显示写一个 Manager 的实例,则 Django 会默认分配一个,如果我们显示得写了一个,那么就不会有默认分配的。这个是怎么做到的。
这个截图涉及到 6 个窗口,读者需要注意一下每个文件,每个窗口下的代码位置,才能做出对应。
【1】尝试将所有包含 contribute_to_class 方法的类属性全部添加到新类中。于是触发了【2】
【2】调用了所有包含 contribute_to_class 方法的类属性的 contribute_to_class 方法,也就是说,只要类属性包含这个方法,我就将其调用一遍。但是需要注意,BaseManager 类也有这个方法,也就是说,如果我们自定义 Model 的时候,添加了 Manager 或其子类的实例,那么将会出发【3】
【3】属于 BaseManager ,紧接着【3】会调用【4】
【5】这里会触发【6】,假如我们自定义了 Manager 的实例,那么【6】的返回结果就不会为空,于是【5】这个判断内部的代码就走不了,所以 Django 就不会再给自定义的 Model 分配一个 Manager 的实例。假如我们没有给自定义的 Model 显示得指定一个 Manager 或者其子类的实例,那么【6】的返回值就为空,于是会走【5】判断逻辑内的代码,于是就会默认分配一个 Manager 的实例。