From 0450aa948a99655943824dfec160c647f7ee8706 Mon Sep 17 00:00:00 2001 From: lkjing2021345 <1908499668@qq.com> Date: Tue, 18 Aug 2026 00:10:31 +0800 Subject: [PATCH] =?UTF-8?q?Add=20Day=5F2:=20Django=E8=A7=86=E8=A7=92?= =?UTF-8?q?=E4=B8=8B=E7=9A=84Python=E5=9F=BA=E7=A1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Day_2/README.md | 141 ++++++++++++++++++++++++++++++++++++++++ Day_2/参考解答.py | 160 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 301 insertions(+) create mode 100644 Day_2/README.md create mode 100644 Day_2/参考解答.py diff --git a/Day_2/README.md b/Day_2/README.md new file mode 100644 index 0000000..bd920ff --- /dev/null +++ b/Day_2/README.md @@ -0,0 +1,141 @@ +# Day_2 · Django 视角下的 Python 基础 + +> 今天还不用学 Django,也不需要会面向对象。 +> 下面的题目只用到基础语法(字典、列表、字符串、函数、异常处理), +> 但每一题都是在模拟后端开发中**真正会遇到**的场景。 +> 做完你会发现:Django 里那些看似神秘的东西,底层其实就是这些基础语法。 + +## 今日知识点 → 以后在 Django / Celery 里对应什么 + +| 今天练的 | 以后在 Django/Celery 里会看到 | +| --- | --- | +| 字典、列表的增删改查 | ORM 查询结果、`request.GET`/`request.POST`、JSON 响应 | +| 字符串分割与格式化 | URL 路由解析、日志输出、模板渲染 | +| 函数、`*args`/`**kwargs` | 视图函数 `def index(request, **kwargs)`、中间件、信号 | +| 异常处理(自定义异常 + try/except) | `Http404`、`ValidationError`、Celery 任务失败与重试 | + +--- + +## 题目一(必做):解析 URL 查询参数 + +**背景**:浏览器访问 `https://example.com/register?username=zhangsan&age=20&city=qingdao`, +`?` 后面的 `username=zhangsan&age=20&city=qingdao` 叫做**查询参数(query string)**。 +Django 里 `request.GET` 就是帮你解析好的字典。今天我们手写一个解析器,看清它背后发生了什么。 + +**要求**:写函数 `parse_query_string(query)`,输入一行查询参数(不含 `?`),返回一个字典。 + +必须处理的边界情况: + +1. 空输入 → 返回空字典 `{}` +2. 重复键,如 `name=a&name=b` → 后者覆盖前者:`{'name': 'b'}` +3. 参数没有 `=`,如 `flag` → 值为空字符串:`{'flag': ''}` +4. 值为空,如 `name=` → `{'name': ''}` + +**输入输出示例** + +``` +输入: username=zhangsan&age=20&city=qingdao +输出: {'username': 'zhangsan', 'age': '20', 'city': 'qingdao'} + +输入: name=a&name=b&flag +输出: {'name': 'b', 'flag': ''} + +输入: (空字符串) +输出: {} +``` + +**进阶(加分)**:URL 编码还原——把值中的 `+` 还原为空格,`%20` 也还原为空格。 +如 `name=Zhang+San` → `{'name': 'Zhang San'}`。 + +> 提示: +> - 先 `split('&')` 把整体切成若干段,再对每一段 `split('=', 1)`。 +> 想想为什么 `maxsplit` 要写成 1?(如果值里本身就含 `=` 会怎样?) +> - 字典赋值 `d[key] = value` 天然处理了"覆盖"和"空值"两种情况。 + +**出题意图**:Django 的 `QueryDict`(`request.GET`)底层干的就是这件事。 +把它吃透,以后看到 `request.GET.get('page')` 就不再是黑魔法,而是老朋友。 + +--- + +## 题目二(必做):万能日志记录器(函数 + *args/**kwargs) + +**背景**:后端程序要记录"哪个函数被调用了、用了多久",这叫**日志(logging)**。 +Django 中间件、Celery 任务、接口调用统计里,到处都是这种"给函数包一层"的操作。 + +**要求**:写函数 `log_call(func)`,它接收一个**函数**作为参数,返回一个**新函数**。 +新函数在被调用时: + +1. 打印 `[LOG] 调用函数 <函数名>,参数为 args=(...), kwargs={...}` +2. 调用原函数,拿到结果 +3. 打印 `[LOG] <函数名> 执行完成,耗时 x.xxxx 秒`(用 `time.time()` 算耗时) +4. 返回原函数的结果 + +参数要用 `*args` 和 `**kwargs` 打包——因为你不知道原函数会接收什么参数。 + +**示例** + +```python +import time + +def add(a, b): + return a + b + +logged_add = log_call(add) +print(logged_add(1, 2)) +# [LOG] 调用函数 add,参数为 args=(1, 2), kwargs={} +# [LOG] add 执行完成,耗时 0.0001 秒 +# 3 +``` + +**进阶(加分)**:给 `log_call` 加第二个参数 `label`(默认空字符串), +打印日志时带上标签,如 `log_call(add, label="核心计算")` → `[核心计算][LOG] ...`。 + +> 提示:如果你已经看过装饰器语法,可以用 `@log_call` 直接写在函数头上; +> 没学过就写成 `log_call(add)(1, 2)` 这种普通调用,两种都行。 + +**出题意图**:Django 视图函数接收 `request`、URL 参数通过 `**kwargs` 传进来; +Celery 的任务、日志中间件全部建立在"函数作为参数 + `*args/**kwargs`"之上。 +这是后端开发里出现频率最高的 Python 技巧,没有之一。 + +--- + +## 题目三(进阶):模拟注册接口的参数校验 + +**背景**:后端接口收到请求后的第一件事永远是**校验参数**。 +字段缺失、格式不对,都要返回明确的错误信息,绝不能带着脏数据往下走。 +Django 里抛 `ValidationError`、Celery 里任务失败后重试——这一切的起点都是 try/except。 + +**要求**: + +1. 定义一个字典模拟前端传来的注册数据: + `data = {"username": "zhangsan", "email": "zs@163.com", "age": "20"}`(注意 age 是字符串) +2. 自定义一个异常类 `ValidationError(Exception)`。 +3. 写函数 `validate_registration(data)`,按以下规则校验,**任何一个不满足就抛 `ValidationError`**: + - `username` 必须存在且长度 ≥ 3 + - `email` 必须存在且同时包含 `@` 和 `.` + - `age` 必须存在、能转成 int、且 `1 <= age <= 120` +4. 在调用方用 try/except 捕获:失败打印 `校验失败: <异常信息>`,成功打印 `注册成功,欢迎 !` + +**进阶(加分)**:把规则本身做成数据——写一个通用函数 +`validate(data, rules)`,其中 `rules` 是一个字典: +`{字段名: (校验函数, 错误信息)}`,返回所有不满足的字段的错误列表。 +(提示:校验函数就是你平时写的普通函数或 lambda。) +这是后端参数校验框架(比如 DRF 的 Serializer)的雏形,能想出来说明你已经开始有"抽象"的意识了。 + +**出题意图**:Django forms / serializers 的校验、Celery 任务的失败处理, +核心模式都是"**校验 → 抛异常 → 上层捕获处理**"。 +今天的题目就是把这个模式焊死在你的肌肉记忆里。 + +--- + +## 完成标准 + +- [ ] 题目一 4 个边界情况全部通过(含进阶则 +1) +- [ ] 题目二能打印出完整的调用日志(含进阶则 +1) +- [ ] 题目三能对"缺字段 / 格式错 / 年龄越界"三种情况分别报错(含进阶则 +1) + +完成后打开 `参考解答.py` 对照。建议看完后关掉文件,自己再默写一遍。 + +> 写给还没开始的你:这三天是递进关系—— +> Day_2 用基础语法模拟后端场景,Day_3 用面向对象复刻 ORM 的雏形,Day_4 真正上手 Django。 +> 每天只做必做题,也足够支撑你开学后跟上节奏。 diff --git a/Day_2/参考解答.py b/Day_2/参考解答.py new file mode 100644 index 0000000..175daf1 --- /dev/null +++ b/Day_2/参考解答.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +""" +Day_2 参考解答 +============= +今天的题目只用基础语法,但每个都是后端开发的真实场景。 +建议:先自己写,写不出来再看;看完后把代码关掉,自己再默写一遍。 +""" + +import time + +# ============================================================ +# 题目一:解析 URL 查询参数 +# ============================================================ + +def parse_query_string(query): + """把 'username=zhangsan&age=20' 解析成 {'username': 'zhangsan', 'age': '20'}""" + result = {} + if not query: # 边界 1:空输入 + return result + for item in query.split('&'): # 先按 & 切成若干段 + if '=' in item: + key, value = item.split('=', 1) # maxsplit=1:值里可能也含有 '=',只切第一个 + else: + key, value = item, '' # 边界 3:没有 = 的参数,值记为空字符串 + result[key] = value # 字典赋值天然处理"重复键覆盖"和"值为空" + return result + + +# 进阶:URL 编码还原(把 + 和 %20 还原成空格) +def url_decode(value): + return value.replace('+', ' ').replace('%20', ' ') + + +def parse_query_string_advanced(query): + result = {} + if not query: + return result + for item in query.split('&'): + if '=' in item: + key, value = item.split('=', 1) + else: + key, value = item, '' + result[key] = url_decode(value) + return result + + +# 一个小测试 +if __name__ == '__main__': + print(parse_query_string('username=zhangsan&age=20&city=qingdao')) + # {'username': 'zhangsan', 'age': '20', 'city': 'qingdao'} + print(parse_query_string('name=a&name=b&flag')) + # {'name': 'b', 'flag': ''} + print(parse_query_string('')) + # {} + print(parse_query_string_advanced('name=Zhang+San&city=A%20B')) + # {'name': 'Zhang San', 'city': 'A B'} + + +# ============================================================ +# 题目二:万能日志记录器 +# ============================================================ + +def log_call(func, label=""): + """给函数包一层日志。func 被调用时打印参数和耗时。""" + def wrapper(*args, **kwargs): + tag = f"[{label}] " if label else "" + print(f"{tag}[LOG] 调用函数 {func.__name__},参数为 args={args}, kwargs={kwargs}") + start = time.time() + result = func(*args, **kwargs) # 原样透传参数 + elapsed = time.time() - start + print(f"{tag}[LOG] {func.__name__} 执行完成,耗时 {elapsed:.4f} 秒") + return result # 原样返回结果 + return wrapper + + +@log_call # 等价于 add = log_call(add) +def add(a, b): + return a + b + + +def multiply(a, b): + return a * b + +multiply = log_call(multiply, label="核心计算") # 进阶:带标签 + + +if __name__ == '__main__': + print(add(1, 2)) + print(multiply(3, 4)) + # 输出示例: + # [LOG] 调用函数 add,参数为 args=(1, 2), kwargs={} + # [LOG] add 执行完成,耗时 0.0000 秒 + # 3 + # [核心计算][LOG] 调用函数 multiply,参数为 args=(3, 4), kwargs={} + # [核心计算][LOG] multiply 执行完成,耗时 0.0000 秒 + # 12 + + +# ============================================================ +# 题目三:模拟注册接口的参数校验 +# ============================================================ + +class ValidationError(Exception): + """自定义校验异常:参数不合法时抛出""" + pass + + +def validate_registration(data): + username = data.get("username") + email = data.get("email") + age = data.get("age") + + if not username or len(username) < 3: + raise ValidationError("用户名必须存在且长度不少于 3") + if not email or "@" not in email or "." not in email: + raise ValidationError("邮箱必须包含 @ 和 .") + if age is None: + raise ValidationError("年龄必须存在") + try: + age_int = int(age) # 字符串 "20" 也能转 + except (TypeError, ValueError): + raise ValidationError("年龄必须是整数") + if not (1 <= age_int <= 120): + raise ValidationError("年龄必须在 1~120 之间") + + +def register(): + data = {"username": "zhangsan", "email": "zs@163.com", "age": "20"} + try: + validate_registration(data) + print(f"注册成功,欢迎 {data['username']}!") + except ValidationError as e: + print(f"校验失败: {e}") + + +# ---- 进阶:把规则做成数据 ---- +def is_valid_email(email): + return isinstance(email, str) and "@" in email and "." in email + + +def validate(data, rules): + """rules: {字段名: (校验函数, 错误信息)},返回所有错误列表""" + errors = [] + for field, (check, message) in rules.items(): + if not check(data.get(field)): + errors.append(f"{field}: {message}") + return errors + + +RULES = { + "username": (lambda v: isinstance(v, str) and len(v) >= 3, "用户名长度不足 3"), + "email": (is_valid_email, "邮箱格式不正确"), + "age": (lambda v: isinstance(v, int) and 1 <= v <= 120, "年龄非法"), +} + + +if __name__ == '__main__': + register() # 注册成功,欢迎 zhangsan! + print(validate({"username": "zs", "email": "bad", "age": 200}, RULES)) + # ['username: 用户名长度不足 3', 'email: 邮箱格式不正确', 'age: 年龄非法']