# -*- 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"