Files
DailyChallenge_ProgrammingD…/Day_6/参考解答.py
T

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"