DC's blog DC's blog
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)

DC

愿我一生欢喜,不为世俗所及.
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)
  • Django

  • 第一次学drf

  • 第二次学drf

    • drfCBV
    • 认证
    • 权限
    • 限流
    • 版本
    • 解析器
    • 元类
    • 序列化使用
    • 序列化源码
    • 验证&源码
    • 序列化+验证
    • Serializer案例
    • Serializer总结
      • 序列化Demo
        • 示例代码
        • 添加实验数据
        • 数据库数据
        • 实验结果
      • 序列化
      • 验证+存储
      • 验证+存储+序列化
    • 分页
    • 视图+路由+过滤
    • 练习+跨域问题
    • 博客练习
    • 源码分析汇总
  • 温故知新

  • flask

  • 后端
  • 第二次学drf
DC
2023-11-28
目录

Serializer总结

关于序列化器里的上下文

- ser的context上下文在Field类的context方法里.(从这里开始分析!)
  class Field:
      @property
      def root(self):
        root = self
        while root.parent is not None:
            root = root.parent
        return root
      
      @property
      def context(self):
          return getattr(self.root, '_context', {})
        
- 在BaseSerializer的__init__里有这行代码,意味着序列化器类的实例可以传context参数!!
  self._context = kwargs.pop('context',{})  # ★ 看到没,一开始它就是个字典!
  eg: 阅读视图类GenericAPIView的get_serializer方法的源码,该方法中的context上下文就是这样使用的!!!
    
- 如何使用呢?伪代码如下:
# 序列化器类里
class LoginSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.User
        fields = ['username', 'password']
    def validate(self, attrs):
        token = "..."
        self.context['token'] = token  # ★★★ 给context字典赋值
        return attrs
# 视图类里
class LoginView(ViewSet):
    def login(self, request):
        ser = serializer.LoginSerializer(data=request.data)
        if ser.is_valid():
            token = ser.context.get('token')  # ★★★ 在context字典中取值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

另外需注意的点

- ser.validated_data 是在ser.is_valid 校验通过后使用的, 若在钩子函数中使用ser.validated_data 会报错!!
- 若想在某个字段钩子函数中使用上一个字段钩子校验通过的值,可以从 ser.initial_data中取到上一个字段的值!!
  虽然ser.initial_data是前端传过来的原始数据,但能执行到某个字段的钩子,那么证明上一个字段的校验肯定通过了的,
  所以直接用ser.initial_data里面那么“对应上一个字段”的值,是没有任何问题的!
1
2
3
4

# 序列化Demo

# 示例代码

image-20231103152802983

# 添加实验数据

你把ORM表给ChatGpt, 它会自动帮你生成, 哈哈哈哈!太省事了.

image-20231103154354761

# 数据库数据

image-20231103153221861

# 实验结果

查询一条数据, 这样写接口是不规范的.. 但这里不是重点探究这个,将就下吧.

image-20231103153757115


# 序列化

每一步详细的源码过程可以翻阅 序列化源码.md !!

我只能说, 总结的很完美!! 哈哈哈哈哈.

image-20231103152802983

下方是单条数据序列化的流程图!!

image-20231103224507673

详细说明如下:

1. 创建字段对象 
   - _creation_counter 计数器
2. 元类创建序列化器类 
   - 剔除 类成员“字段类型的类变量” 加到 新增的类成员_declared_fields中, 其余类成员Meta等不变
3. 序列化器类实例化创建对象
   - instance=instance many = False => UserSerializer类的实例
   - instance=queryset many = True  => ListSerializer类的实例,该实例有成员UserSerializer类的实例!!
4. ser.data开始序列化 以单条数据为例 <下面总结顺序跟看源码的流程相反,逆向思维,更容易理解>
1> @cached_property   ★ 该方法被它装饰了哦!!
   ser.fields         获取所有经过bind处理后的字段对象
   [1] ser.get_fields 获得所有字段对象
        注: 在获取所有字段对象阶段, 你序列化器类里定义了啥字段类型的类变量都可以,都不会报错..
        [1-1] ser.get_field_names 获取所有的字段名 (请翻阅,该小节总结的规则)
         特地说下filed和exclude
         - filed = [类变量名些“必须写进来”、ORM表中的字段名 无其他选择]
         - exclude = ORM表中的一些字段名(需排除通过类变量重写的字段名) 无其他选择
         ★ ser.get_field_names返回的字段名集合 必定 小于等于/包含于等于 [字段类型的类变量+ORM表中的字段名]
        [1-2] 将上一步获得的字段名分为两组 注:第一组优先级高于第二组,因为类变量可能会跟ORM表中的字段名重名.
         - 第一组, 字段类型的类变量 保留该字段对象
         - 第二组, ORM表中的字段名 db匹配自动创建字段对象 (PS,ORM表中字段的verbose_name对应创建的字段对象的label属性)
   [2] bind 将所有字段对象经过bind处理
        [2-1] bind操作 处理字段对象的一些属性 field_name、parent、label、source、source_attrs
         - 如何处理的? (请翻阅,该小节总结的规则)
2> ser._readable_fields 筛选出not write_only,即可序列化的字段对象
3> 对筛选后的这些字段对象进行循环
   ret = OrderedDict()
   for field in fields:
       # eg1: attribute = <UserInfo object>.depart.title
       # eg2: attribute = instance
       # 特别注意,它使用的是source_attrs !!!
       attribute = field.get_attribute(instance)  # 得到的结果 可能是数据库中字段对应的值、也可能是instance一条记录.
       # eg1: ret["depart"] = str(<UserInfo object>.depart.title)
       # eg2: ret["Xxx"] = SerializerMethodField().to_representation(instance)
       ret[field.field_name] = field.to_representation(attribute)
   - 需要特别注意的是 SerializerMethodField 类型的字段对象!!序列化的自定义字段绑定的方法的value参数是数据库中的一条记录.

==||++ 若是多条数据,就多了个循环,注意ListSerializer类的实例里child成员!!



- ser.get_fields的结果/返回值 "获得的所有字段对象"
OrderedDict([
  ('id', IntegerField(label='ID', read_only=True)), 
  ('gender', CharField(source='get_gender_display')), 
  ('depart', CharField(source='depart.title')),
  ('ctime', DateTimeField(format='%Y-%m-%d')), 
  ('xxx', SerializerMethodField()), 
  ('tag', SerializerMethodField()), 
  ('name', CharField(label='姓名', max_length=32)),
  ('age', IntegerField(label='年龄'))
])

  
- ser.fields的结果/返回值 "经过bind处理后的所有字段对象" “字段对象里的field_name就是字段名!”
{
   'id': IntegerField(label='ID', read_only=True), 
   'gender': CharField(source='get_gender_display'), 
   'depart': CharField(source='depart.title'), 
   'ctime': DateTimeField(format='%Y-%m-%d'), 
   'xxx': SerializerMethodField(), 
   'tag': SerializerMethodField(), 
   'name': CharField(label='姓名', max_length=32),  
   'age': IntegerField(label='年龄')
}
你别看上面打印的结果没啥改变,循环打印每个字段对象中的这些属性field_name、label、source、source_attrs,就原形毕露了!
(PS:打印字段对象的parent属性会一直递归,不知啥原因,暂且不管.)
IntegerField(label='ID', read_only=True)  id ID id ['id']
CharField(source='get_gender_display')    gender Gender get_gender_display ['get_gender_display']
CharField(source='depart.title')          depart Depart depart.title ['depart', 'title']
DateTimeField(format='%Y-%m-%d')          ctime Ctime ctime ['ctime']
SerializerMethodField()                   xxx Xxx * []
SerializerMethodField()                   tag Tag * []
CharField(label='姓名', max_length=32)     name 姓名 name ['name']
IntegerField(label='年龄')                 age 年龄 age ['age']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

# 验证+存储

image-20231109151219177

来抓一抓错误

class ValidationError(APIException):
    status_code = status.HTTP_400_BAD_REQUEST
    default_detail = _('Invalid input.')
    default_code = 'invalid'

    # 看到没,detail可以位置传参传过去!
    def __init__(self, detail=None, code=None):
        if detail is None:
            detail = self.default_detail
        if code is None:
            code = self.default_code

        if isinstance(detail, tuple):
            detail = list(detail)
        elif not isinstance(detail, dict) and not isinstance(detail, list):
            detail = [detail]

        self.detail = _get_error_details(detail, code)

先看图中的右下角的run_validators函数, errors=[] 字段自身的校验,有多个规则不满足,不满足的都会收集到
然后该函数 raise ValidationError(errors) 在Serializer类中的to_internal_value被捕获
特别注意的是, errors[field.field_name] = exc.detail  ★看到没,错误信息的key是字段名!! 而且try..except..处于for循环里.
最后,与raise ValidationError(errors) 在BaseSerializer类中的is_valid被捕获
So,举个例子,字段自身校验不通过,ser.errors的值像这个样子:
  {'mobile': [ErrorDetail(string='该字段不能为空。', code='blank')], 
   'password': [ErrorDetail(string='该字段不能为空。', code='blank')]}
只不过,后端通过Response处理后返回给前端,前端拿到的是 {'mobile': ['该字段不能为空。'], 'password': ['该字段不能为空。']}

那关于全局钩子中的报错呢?你看源码,关键在于这行语句
raise ValidationError(detail=as_serializer_error(exc)) 细看as_serializer_error

def as_serializer_error(exc):
    assert isinstance(exc, (ValidationError, DjangoValidationError))

    if isinstance(exc, DjangoValidationError):
        detail = get_error_detail(exc)
    else:
        detail = exc.detail

    if isinstance(detail, Mapping):
        return {
            key: value if isinstance(value, (list, Mapping)) else [value]
            for key, value in detail.items()
        }
    elif isinstance(detail, list):
        return {
            api_settings.NON_FIELD_ERRORS_KEY: detail  # ★★★
        }
    return {
        api_settings.NON_FIELD_ERRORS_KEY: [detail]  # ★★★
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

值得注意的点:

- 若ORM表中写了这样的字段
    ext1 = models.CharField(verbose_name="格外1", max_length=11, blank=True, null=True)
    ext2 = models.IntegerField(verbose_name="格外2", blank=True, null=True)
  ★ 通过db匹配后,会自动生成的这样的字段对象: !!
    CharField(allow_blank=True, allow_null=True, label='格外1', max_length=11, required=False)
    IntegerField(allow_null=True, label='格外2', required=False)
    看到没,required=False意味检验的话,前端不是必传.
    另外生成的字段对象的default值为<class 'rest_framework.fields.empty'>
  ★ 经过ser.data序列化后,ext1和ext2字段值的结果都是null
  关于这个,有点模糊不清,做项目时再探究.(可先看下官方文档)
  https://q1mi.github.io/Django-REST-framework-documentation/api-guide/fields_zh/#allow_null
  https://q1mi.github.io/Django-REST-framework-documentation/api-guide/fields_zh/#charfield
- 若序列化器类写了额外字段 more = serializers.CharField(default="默认")
  那么经过__init__实例化后,该字段required=False,default="默认"
- 若ORM表中写了这样的字段
  ext3 = models.IntegerField(verbose_name="格外3",default=25)
  ★ 通过db匹配后,会自动生成的这样的字段对象: !!
  IntegerField(label='格外3', required=False) 另我很疑惑的是,其default值是 <class 'rest_framework.fields.empty'>
  
- save操作
    def create(self, validated_data):
        book = Book.objects.create(**validated_data)
        return book
      
    def update(self, instance, validated_data):
        instance.name = validated_data.get('name', instance.name)
        instance.price = validated_data.get('price', instance.price)
        instance.publish = validated_data.get('publish', instance.publish)
        instance.save()
        return instance 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 验证+存储+序列化

image-20231109151435224

image-20231112200414491

Ps: 上图的更新还有个奇淫技巧 instance.__dict__.update(**validated_data)

分析下整体过程

序列化器中类变量以及Meta中写的那些字段,只能决定 有哪些字段对象可以拿来用.
read_only和write_only才决定了哪些字段拿来序列化,哪些拿来验证. 拿来验证的字段才是必须要传的!

= 1.纯序列化单条记录 = 
ser = ExampleModelSerializer(instance=instance) 
ser.data

= 2.纯序列化多条记录=
ser = ExampleModelSerializer(instance=queryset,many=True)
ser.data

= 3.验证前端传递的数据 - 保存数据库 - 序列化 =
ser = ExampleModelSerializer(data=request.data) 
ser.is_valid()  
ser.data

= 4.验证前端传递的数据 - 更新数据库 - 序列化 =
ser ExampleModelSerializer(data=request.data,instance=instance)
ser.is_valid()  
ser.data


【壹壹壹】
上述的4种情况,无论哪一种,都会先经历三步!
1> 创建字段对象
   以前字典是无序的.所以里面有个计数器,表明先后创建的顺序.
2> 基于元类创建序列化器类
   '_declared_fields' -> {'name':IntegerField字段对象,'age':CharField字段对象}
   注意其key值是类变量的变量名,value值是该类变量名对应的值.
3> 创建序列化器的对象
   主要是根据many=True和many=False创建的对象不同.毕竟new方法返回啥,该实例对象就是啥.
   ExampleModelSerializer()  or  ListSerializer(child=ExampleModelSerializer())


【贰贰贰】
ok,前面三步进行完后,会执行ser.data或者ser.is_valid(),我们继续寻找共同点!
只要是执行ser.data或者执行ser.is_valid(),都会经历这三步.
★★★ 本质就是在执行Serializer类中被@cached_property装饰的fields方法!!
    @cached_property会将fields方法的执行结果放到ser的__dict__中, 即ser.__dict__["fields"] = 结果
		★★★ So, ser.is_valid()后再ser.data,不会再执行一遍 以下三步/fields方法! 而是会直接去__dict__中取.
1> 获得所有的字段名field_names = 获取序列化器类中类变量的变量名 + 序列化器类使用的model表中的所有字段名
   注意: 
      若写"__all__",是 类变量名 + db中字段名, field_names列表中,还有可能重复
      若fields指定了字段,[那么额外字段得写在里面],因为 field_names列表就是fields的值.
2> 会循环field_names获得所有的字段对象 = 
         '_declared_fields'中的字段对象原封不动 + 匹配数据库自动创建的字段对象(注:字段名与类变量重复了不会匹配创建)
   所有字段对象 {'类变量变量名':字段对象,'数据库中的字段名':匹配自动创建字段对象}
3> 会循环所有字段对象,执行Field类里的bind方法! 
   bind(self, field_name, parent)   field_name就是循环的每一项的key值,parent就是序列化器类实例
   - 关于字段对象里的成员 field_name 等于 key值,就是字段名
   - 关于字段对象里的成员 source
     - 字段对象有source的 source值不变
     - 字段对象有没有设置source的 字段对象的source等于field_name,即字段名
   - 关于字段对象里的成员 source_attrs
     - source_attrs = source.split('.') 
       'depart.title' -> ['depart','title']  'get_gender_display' -> ["get_gender_display"] 'name' -> ['name']

      
      
ok,经过上述流程,提醒一点序列化器类实例化时候传入的 instance/data 里实参都还没有咋使用.
              只是序列化器类实例的实例里躺着两个成员 self.instance 和 self.initial_data

  

开始分情况讨论
= 情况1/2,纯序列化 = 注:无论单条还是多条数据,本质都是一样的. 假如"instance是UserInfo表的一条记录<UserInfo object>"
  ser = ExampleModelSerializer(instance=instance)  # 1
  ser.data  # 2
  
  ○ 执行第1条语句ser = ExampleModelSerializer(instance=instance)时先执行【壹壹壹】
  ○ 执行第2条语句时ser.data先执行【贰贰贰】, 接着
  step1> 接着,会再次循环所有字段对象, 不要write_only=True的字段对象.
         得到可用于序列化的字段对象 [字段对象1,字段对象2,字段对象3...]
  step2> 接着,循环 可用于序列化的字段对象 [字段对象1,字段对象2,字段对象3...]
         关键在于执行两行代码.
         """假比,instance是UserInfo表的一条记录<UserInfo object>,该表的Fk字段depart,有choice属性的字段gender
         ret["depart"] = str(<UserInfo object>.depart.title)
         ret["gender"] = str(<UserInfo object>.get_gender_display())
         """
         - attribute = field.get_attribute(instance)
         - ret[field.field_name] = field.to_representation(attribute)

= 情况3,验证前端传递的数据 - 保存数据库 - 序列化 =  
  ser = ExampleModelSerializer(data=request.data)  # 1
  ser.is_valid()  # 2
  ser.data  # 3
  
  ○ 执行第1条语句ser = ExampleModelSerializer(data=request.data)时先执行【壹壹壹】
  ○ 执行第2条语句时ser.is_valid()先执行【贰贰贰】,接着
  step1> 接着,会再次循环所有字段对象, 不要read_only=True的字段对象.
         得到可用于验证的字段对象 [字段对象1,字段对象2,字段对象3...]
  step2> for 字段对象 in 可用于验证的字段对象 [字段对象1,字段对象2,字段对象3...]
             先会通过伪代码 value = data[field.field_name] 拿到当前字段对象在前端传递的Json数据中对应的值!
             校验字段自己的规则 - 检验成功,返回value
             执行字段的钩子函数 - 上一步返回的value传递给字段钩子函数,检验成功,返回value
             在最后,执行了set_value(ret, field.source_attrs, value)
         上述过程,相当于 有个字典validated_data,每次循环都会执行 validated_data[field.source_attrs] = value
  step3> 执行全局钩子函数 其参数是validated_data. - 校验通过,返回的也是validated_data
  step4> 执行save() 也就是执行 self.instance =  self.create(validated_data, **kwargs) 
         ★ 注意啊!self是ser,所以ser中的instance成员有值啦!!!
  就此,验证并存储数据库执行完毕
  ○ 执行第3条语句ser.data时不会重新执行[贰贰贰],它直接拿到ser.is_valid()执行[贰贰贰]时的结果!
  step5> 开始 情况1/2 纯序列化中的 第一步和第二步.
  
= 情况4,验证前端传递的数据 - 更新数据库 - 序列化 =
  ser ExampleModelSerializer(data=request.data,instance=instance)  # 1
  ser.is_valid()  # 2
  ser.data  # 3
  
  其实与情况3整体并无不同,仅仅只是在save时候
                      是self.instance = self.update(self.instance, {**validated_data, **kwargs})
  也就是说,传入的instance只是在更新数据的时候使用了下!! 
  后续执行ser.data时使用的是 重新赋值后的 self.instance!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

★★★ 粘贴一下非常重要的源码.

class BaseSerializer(Field):
    def __init__(self, instance=None, data=empty, **kwargs):
        self.instance = instance
        if data is not empty:
            self.initial_data = data
        self.partial = kwargs.pop('partial', False)
        self._context = kwargs.pop('context', {})
        kwargs.pop('many', None)
        super().__init__(**kwargs)
        
    def __new__(cls, *args, **kwargs):
        if kwargs.pop('many', False):
            # - 决定了序列化器类实例化时返回的实例是什么.
            return cls.many_init(*args, **kwargs)
        return super().__new__(cls, *args, **kwargs)
        
    @property
    def data(self):
        if not hasattr(self, '_data'):
            if self.instance is not None and not getattr(self, '_errors', None):
                # ser=A(instance=instance) ser.data
                # ser=A(data=request.data) - ser.is_valid() -  save() - ser.data
                # ser=A(data=request.data,instance=instance) -  ser.is_valid() - save() - ser.data
                # 上面三个流程,执行ser.data时,都会执行这里.
                # 只不过,self.instance不一样,分别是 传入的/新增的/更新的! 因为运行save时,将结果赋值给了self.instance.
                # - ★ 跳转到Serializer的to_representation方法 里面有条重要语句 fields = self._readable_fields !!!
                self._data = self.to_representation(self.instance)
            elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
                # ser=A(data=request.data) ser.is_valid() 没有save 直接ser.data时执行!!
                self._data = self.to_representation(self.validated_data)
            else:
                # 其余情况 比如: ser=A() ser.data
                self._data = self.get_initial()
        return self._data
      
    def is_valid(self, *, raise_exception=False):
        # - 注意,重复多次校验,只会走一次
        if not hasattr(self, '_validated_data'):
            try:
                # - ★ 开始校验 跳转到Serializer的run_validation方法
                #     里面有个重要语句 self.to_internal_value(data) 
                #                    - 然后在to_internal_value方法中,fields = self._writable_fields!!!
                self._validated_data = self.run_validation(self.initial_data) 
            except ValidationError as exc:
                self._validated_data = {}
                self._errors = exc.detail
            else:
                self._errors = {}

        if self._errors and raise_exception:
            raise ValidationError(self.errors)
        return not bool(self._errors)
      
    def save(self, **kwargs):
        validated_data = {**self.validated_data, **kwargs}

        # ★该self就是ser,给ser.instance赋值啦!!
        if self.instance is not None:
            self.instance = self.update(self.instance, validated_data)  # -- 更新
            assert self.instance is not None, (
                '`update()` did not return an object instance.'
            )
        else:
            self.instance = self.create(validated_data)  # -- 新增
            assert self.instance is not None, (
                '`create() did not return an object instance.'
            )
        return self.instance
      
    @property
    def validated_data(self):
        if not hasattr(self, '_validated_data'):
            msg = 'You must call `.is_valid()` before accessing `.validated_data`.'
            raise AssertionError(msg)
        return self._validated_data  #  返回所有校验通过的前端传递的数据 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

方法的参数、返回值; 当前self是谁, 赋值操作时, 给它添加了啥成员;@cached_property; 这些就是我此次梳理源码后的心得!!


Serializer案例
分页

← Serializer案例 分页→

最近更新
01
deepseek本地部署+知识库
02-17
02
实操-微信小程序
02-14
03
教学-cursor深度探讨
02-13
更多文章>
Theme by Vdoing | Copyright © 2023-2025 DC | One Piece
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式