Files
DailyChallenge_ProgrammingD…/Day_6/README.md
T

3.2 KiB
Raw Blame History

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

要求:

  1. 定义字段类型类(都实现 __get__ 和 __set__):

    • CharField(max_length):__set__ 里校验"必须是字符串、长度 ≤ max_length",否则抛 ValueError
    • EmailField:继承 CharField,额外校验必须含 @ 和 .
    • IntegerField(min_value, max_value):校验"必须是 int(注意 True 不算)、在范围内"

    值的存储要写到实例自己的 __dict__ 里;还没赋过值时返回一个哨兵对象 (用一个模块级 _NOT_SET = object())。__get__ 里 instance is None 表示 "通过类访问",此时应返回描述符本身(这样 Article.title 看到的是 Field 对象, 这正是 Django 里 Article.title 的行为)。

  2. 写一个基类 ModelBase,用 __init_subclass__(在子类定义完成后自动调用) 遍历子类的 cls.__dict__,把每个 Field 对象的 .name 填上它的属性名。 这样描述符才知道自己对应哪个属性。

  3. 定义 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)
    
  4. 测试:

    • 正常赋值、读取,一切正常
    • 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 的"基础 → 函数 → 面向对象"闭环就完成了。