Day_6 · 面向对象(三):复刻 models.CharField(进阶)
这是整个系列最难的一题,做不出来很正常——卡住就回头把 Day_4 / Day_5 再过一遍。 Django 里这样声明字段:
class Article(models.Model): title = models.CharField(max_length=50)
title是类属性,但每个实例的title值又各自不同——这是怎么做到的? 答案是 descriptor(描述符)协议:类属性如果是实现了__get__/__set__的对象, 那么对实例属性的一切读写都会先经过它。Django 的 Field 就是这种对象。
今日知识点 → 以后在 Django 里对应什么
| 今天练的 | 以后在 Django 里会看到 |
|---|---|
descriptor(__get__ / __set__) |
Django Field 的核心机制 |
| 类属性声明字段 | models.CharField(max_length=50) 的真相 |
__init_subclass__ 元编程 |
框架"自动注册"类信息的通用手段 |
题目(必做):类属性当"字段声明"——复刻 models.CharField
要求:
-
定义字段类型类(都实现
__get__和__set__):CharField(max_length):__set__里校验"必须是字符串、长度 ≤ max_length",否则抛ValueErrorEmailField:继承CharField,额外校验必须含@和.IntegerField(min_value, max_value):校验"必须是 int(注意True不算)、在范围内"
值的存储要写到实例自己的
__dict__里;还没赋过值时返回一个哨兵对象 (用一个模块级_NOT_SET = object())。__get__里instance is None表示 "通过类访问",此时应返回描述符本身(这样Article.title看到的是 Field 对象, 这正是 Django 里Article.title的行为)。 -
写一个基类
ModelBase,用__init_subclass__(在子类定义完成后自动调用) 遍历子类的cls.__dict__,把每个 Field 对象的.name填上它的属性名。 这样描述符才知道自己对应哪个属性。 -
定义
Article(ModelBase),用类属性声明字段:class Article(ModelBase): title = CharField(max_length=50) content = CharField(max_length=1000) author_email = EmailField(max_length=100) views = IntegerField(min_value=0) -
测试:
- 正常赋值、读取,一切正常
a.title = "x" * 100→ 抛ValueError(超长)a.views = -5→ 抛ValueError(越界)a.author_email = "not-an-email"→ 抛ValueError(格式错)print(Article.title)→ 打印出的是CharField对象本身
出题意图:这就是 Django Field 的核心机制。做完这一题,你再去看
models.CharField(max_length=50) 时,看到的就不是魔法,而是你今天亲手写过的代码。
完成标准
- 三种 Field 的校验全部生效
- 类访问(
Article.title)返回描述符本身
完成后打开 参考解答.py 对照。
进度提示:Day_6 是整个系列里最难的一题,卡住很正常。 它对应的正是后面写真正 Django Model 时会遇到的机制。 这一关过了,Day_2~Day_6 的"基础 → 函数 → 面向对象"闭环就完成了。