89 lines
3.3 KiB
Python
89 lines
3.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Day_6 参考解答
|
|
=============
|
|
主题:面向对象(三)—— descriptor 复刻 models.CharField(进阶)。
|
|
建议:先自己写,写不出来再看;看完后关掉文件自己默写一遍。
|
|
"""
|
|
|
|
_NOT_SET = object() # 哨兵值:表示"字段还没赋过值"
|
|
|
|
|
|
class CharField:
|
|
def __init__(self, max_length=255):
|
|
self.max_length = max_length
|
|
self.name: str | None = None # 字段对应的属性名,由 ModelBase.__init_subclass__ 填入
|
|
|
|
def __get__(self, instance, owner):
|
|
if instance is None:
|
|
return self # 通过类访问(Article.title):返回描述符本身
|
|
return instance.__dict__.get(self.name, _NOT_SET)
|
|
|
|
def __set__(self, instance, value):
|
|
if not isinstance(value, str):
|
|
raise ValueError(f"{self.name} 必须是字符串")
|
|
if len(value) > self.max_length:
|
|
raise ValueError(f"{self.name} 长度不能超过 {self.max_length}")
|
|
instance.__dict__[self.name] = value # 存到实例自己的字典里
|
|
|
|
|
|
class EmailField(CharField):
|
|
def __set__(self, instance, value):
|
|
if "@" not in value or "." not in value:
|
|
raise ValueError(f"{self.name} 不是合法的邮箱格式")
|
|
super().__set__(instance, value) # 通过校验后再走父类存储
|
|
|
|
|
|
class IntegerField:
|
|
def __init__(self, min_value=None, max_value=None):
|
|
self.min_value = min_value
|
|
self.max_value = max_value
|
|
self.name: str | None = None
|
|
|
|
def __get__(self, instance, owner):
|
|
if instance is None:
|
|
return self
|
|
return instance.__dict__.get(self.name, _NOT_SET)
|
|
|
|
def __set__(self, instance, value):
|
|
# bool 是 int 的子类,所以要显式排除 True/False
|
|
if not isinstance(value, int) or isinstance(value, bool):
|
|
raise ValueError(f"{self.name} 必须是整数")
|
|
if self.min_value is not None and value < self.min_value:
|
|
raise ValueError(f"{self.name} 不能小于 {self.min_value}")
|
|
if self.max_value is not None and value > self.max_value:
|
|
raise ValueError(f"{self.name} 不能大于 {self.max_value}")
|
|
instance.__dict__[self.name] = value
|
|
|
|
|
|
class ModelBase:
|
|
# 子类定义完成后自动调用:把每个 Field 对象和它的属性名绑定
|
|
def __init_subclass__(cls, **kwargs):
|
|
super().__init_subclass__(**kwargs)
|
|
for attr_name, attr_value in cls.__dict__.items():
|
|
if isinstance(attr_value, (CharField, IntegerField)):
|
|
attr_value.name = attr_name
|
|
|
|
|
|
# 题目描述里想定义哪个类都可以(Article/Post 皆可),机制完全相同
|
|
class Post(ModelBase):
|
|
title = CharField(max_length=50)
|
|
content = CharField(max_length=1000)
|
|
author_email = EmailField(max_length=100)
|
|
views = IntegerField(min_value=0)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
a = Post()
|
|
a.title = "Django 入门"
|
|
a.content = "跟着 Day_6 慢慢理解框架"
|
|
a.author_email = "zs@163.com"
|
|
a.views = 10
|
|
print(a.title, a.views, a.author_email) # Django 入门 10 zs@163.com
|
|
print(Post.title) # 类访问 => CharField 对象本身
|
|
|
|
# 以下三条都应该抛 ValueError,逐行取消注释验证
|
|
# a.title = "x" * 100
|
|
# a.views = -5
|
|
# a.author_email = "not-an-email"
|