# Day_3 · 面向对象 × 手写一个小 ORM > Django 最核心的能力是 **ORM(对象关系映射)**:你定义类,Django 帮你管理数据库表。 > 打开任何 Django 项目,第一行几乎都是 `class Article(models.Model):`—— > 这一行的背后,是整片面向对象的大海。 > 今天我们不装 Django,用纯 Python 把它的 Model 一层层拆开看: > 类、实例、继承、魔法方法、property、描述符(descriptor)。 > 全部做完,你会第一次"看穿"框架的魔法。 ## 今日知识点 → 以后在 Django 里对应什么 | 今天练的 | 以后在 Django 里会看到 | | --- | --- | | 类定义 + 类属性 | `class Article(models.Model)`,字段就是类属性 | | `__init__` / 实例方法 | Model 实例的构造、`.save()` / `.delete()` | | `__str__` / `__repr__` 魔法方法 | Admin 后台显示的名称、QuerySet 打印结果 | | 继承与 `super()` | 所有 Model 继承 `models.Model`;自定义 Manager | | `@property`、描述符与封装 | Model 上的派生字段、字段校验(Field 机制) | --- ## 题目一(必做):设计一个 User 类 **背景**:Django 里定义一个用户模型只需要三行: ```python class User(models.Model): name = models.CharField(max_length=50) email = models.EmailField() ``` 看起来像魔法,其实核心就是"类 + 类属性"。今天我们不用 Django,用纯 Python 复刻 User 类。 **要求**: 1. 类属性 `all_users = []`(模拟数据库表,用来存所有创建出来的用户) 2. `__init__(self, name, email, age)`:把三个参数存成实例属性,并把 `self` 追加进 `all_users` 3. 实例方法 `get_full_name()`:返回 `用户:()` 4. 魔法方法 `__str__(self)`:返回 `self.name`(Django admin 后台显示的就是它!) 5. 魔法方法 `__repr__(self)`:返回 `User(name='zhangsan', email='zs@163.com', age=20)` 这种"能还原出原对象"的格式 **验证**: - 创建 3 个用户,打印 `User.all_users` - 分别 `print` 每个用户(会触发 `__str__`) - 用 `eval(repr(u1))` 还原出 u1 的信息,确认内容一致 **思考题(写在注释里)**:`__str__` 和 `__repr__` 到底有什么区别? (提示:一个是"给人看的",一个是"给开发者调试看的"——具体从使用场景和返回值要求去想。) --- ## 题目二(必做):继承 —— 复刻 models.Model **背景**:Django 里 `Article` 没有写 `__init__`,没有写 `save()`,却能 `.save()`、能查 `objects`。 为什么?因为通用能力都在父类 `models.Model` 里,子类只需要写自己的字段。 今天我们自己写这个父类。 **要求**: 1. 定义基类 `BaseModel`: - 类属性 `objects = []`(模拟数据库表) - `save(self)`:把 `self` 追加进 `objects`,并打印 `[DB] 已保存 <类名>: ` - `delete(self)`:把 `self` 从 `objects` 中移除,打印 `[DB] 已删除 ` - `to_dict(self)`:把实例的所有属性转成字典(提示:`self.__dict__` 是什么?) - `__str__(self)`:默认返回 `<类名#id>`,比如 `` 2. 定义 `Article(BaseModel)`:`__init__` 接收 `title, content, author` 存成实例属性, 再用 `time` 模块记录 `created_at`(形如 `2026-08-18 10:30:00`) 3. 定义 `Comment(BaseModel)`:`__init__` 接收 `content, author` 4. 全流程走一遍:创建文章和评论 → save → 遍历 `BaseModel.objects` 打印 → `to_dict()` → delete 5. 验证继承关系:`print(issubclass(Article, BaseModel))` 应为 `True`, `print(isinstance(a, BaseModel))` 应为 `True` **进阶(思考 + 动手)**:如果要求"Article 保存前先打印一行日志",而 `save()` 写在父类里, 怎么办?(提示:在子类里**重写** `save()`,先打印,再 `super().save()`。试试看输出顺序。) **出题意图**:Django 的 Model 就是"通用逻辑放父类,子类只写自己的字段"。 理解了继承 + `super()`,你就理解了为什么 Django 模型"不写 save 却能保存"。 --- ## 题目三(进阶):类属性当"字段声明"——复刻 models.CharField **背景**:Django 里这样声明字段: ```python class Article(models.Model): title = models.CharField(max_length=50) content = models.TextField() ``` `title` 是**类属性**,但每个实例的 `title` 值又各自不同——这是怎么做到的? 答案是 **descriptor(描述符)协议**:类属性如果是实现了 `__get__` / `__set__` 的对象, 那么对实例属性的一切读写都会先经过它。Django 的 Field 就是这种对象。 **要求**: 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)`,用类属性声明字段: ```python 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)` 时,看到的就不是魔法,而是你今天亲手写过的代码。 --- ## 完成标准 - [ ] 题目一:User 类全流程 + 想通了 `__str__` / `__repr__` 的区别 - [ ] 题目二:BaseModel + Article + Comment 全流程 + 继承验证(进阶则 +1) - [ ] 题目三:三种 Field 的校验全部生效(难度较高,做不出来先把题目二的继承吃透) 完成后打开 `参考解答.py` 对照。 > 进度提示:Day_3 的题目三是整个系列里最难的一题,卡住很正常。 > 它对应的正是 Day_4 你真正写 Django Model 时会遇到的机制。