首页 > 其他分享 >07-Model

07-Model

时间:2023-01-24 18:44:06浏览次数:41  
标签:__ .__ 07 自定义 new Model class

下面进入到了 ORM 的学习,ORM 篇幅较长,因为函数太多。我们主要分为两个部分,Model 和 QuerySet

元类

在python中万物皆对象,类也是一个对象,自定义的类或是python中内置的类都是由元类(type)实例化来的。因此元类也是一种类。

元类 ——实例化——> 类 ——实例化——> 对象

元类实例化类

使用元类实例化类需要三个参数:类名,基类(即继承于哪些父类),类名称空间(由类子代码执行得到)

# type是内置元类,调用type可实例化一个自定义的类
type(class_name, class_bases, class_dict)  

原类实例化流程(与类实例化对象的过程一致):

  1. 调用__new__方法产生一个空对象(类)
  2. 调用__init__方法初始化对象(类)
  3. 返回初始化后的对象(类)

__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'}

完整代码

自定义元类流程梳理:

  1. 继承type类
  2. 自定义元类本质上也是一个类,实例化生成一个类,因此会先调用__new__方法产生空对象
  3. 再调用__init__方法初始化空对象
  4. 使用自定义元类实例化出来的类可被调用,因此自定义元类中有__call__方法
  5. 实例化出来的类也可实例化为一个对象,因此__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 的实例。

标签:__,.__,07,自定义,new,Model,class
From: https://www.cnblogs.com/yaowy001/p/17065984.html

相关文章

  • Informer源码学习记录之 "模型搭建build_model"
    一、初始化1.代码结构main_informer.py:  exp=Exp(args)#setexperimentsexp_basic.py:classExp_Informer(E......
  • CodeForces-907#B 题解
    正文设数组\(c_{x,y,i,j}\)代表\((x,y)\)位置的大格子中\((i,j)\)位置的小格子。很显然,输入中字符的输入顺序是要调整的,实际的顺序是\(x,i,y,j\)。对于输入的\(......
  • vue:v-model (原生组件与自定义组件)
    vue2:原生组件 vue2:自定义组件 vue3:自定义组件vue3更改了vue2声明自定义组件的方式,将vue2中的value替换成了modelValue,将emit触发的事件名改为'update:model......
  • Day07 - 面向对象
    1.面向对象概述面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。面向对象是相对于面向过程来讲的,面向对象方法,把相关的数据和方法......
  • Luogu P7191 [COCI2007-2008#6] GRANICA
    https://www.luogu.com.cn/problem/P7191设\(\bmodm=r\),则能得到\(a_i=x_i\timesm+r\)。那么对于相邻的两个数\(a_i,a_{i-1}\)相减,就能得到\((x_i-x_{i-1})\time......
  • .net NPOI导出Excel,自定义单元格背景颜色,office2007及以上,及office2003使用方法
    目录:NPOI相关功能目录开发环境:VS2015.Net版本:.NetFramework4.5.2NPOI版本:2.4.1.0本以为NPOI使用颜色值会非常方便,以为RGB或16进制赋值就行了没想到NPOI不这样给我们用,......
  • 07 do···while循环
    do···while循环代码packagecom.zhan.base_2;publicclassTest07_DoWhile{publicstaticvoidmain(String[]args){//输出1+2+3+···+100......
  • day07-Spring管理Bean-IOC-05
    Spring管理Bean-IOC-053.基于注解配置bean3.3自动装配基本说明:基于注解配置bean,也可以实现自动装配,使用的注解是:@AutoWired或者@Resource@AutoWired的规则说明(1......
  • CU002HModel matching query does not exist.
    问题描述:CU002HModelmatchingquerydoesnotexist.问题分析:匹配的查询不存在。顾名思义就是什么数据都没有。原因是get查询时没有结果会报错,所以有两个选择,添加使用tr......
  • 07重要的数据合并操作
    importnumpyasnp#行合并a=np.arange(6).reshape(2,3)b=np.random.randint(10,20,size=(4,3))print(a)print(b)print('***********************************......