重构: 15天课程体系(从零到Django), 每题先学后做30分钟
This commit is contained in:
@@ -1,91 +0,0 @@
|
||||
# Day_3 · 函数与异常:后端程序的"骨架"和"安全网"
|
||||
|
||||
> 后端代码里出现频率最高的两个技巧:**把函数当参数传来传去**(日志、中间件的底层)
|
||||
> 和 **异常处理**(参数校验、错误响应的底层)。
|
||||
> 今天两道题都只用到基础语法,做完你就掌握了后端开发的两块地基。
|
||||
|
||||
## 今日知识点 → 以后在 Django / Celery 里对应什么
|
||||
|
||||
| 今天练的 | 以后在 Django/Celery 里会看到 |
|
||||
| --- | --- |
|
||||
| 函数作为参数、`*args`/`**kwargs` | 视图函数 `def index(request, **kwargs)`、中间件、Celery 任务 |
|
||||
| 自定义异常 + try/except | `Http404`、`ValidationError`、Celery 任务失败与重试 |
|
||||
|
||||
---
|
||||
|
||||
## 题目一(必做):万能日志记录器(函数 + *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`"之上。
|
||||
|
||||
---
|
||||
|
||||
## 题目二(必做):模拟注册接口的参数校验
|
||||
|
||||
**背景**:后端接口收到请求后的第一件事永远是**校验参数**。
|
||||
字段缺失、格式不对,都要返回明确的错误信息,绝不能带着脏数据往下走。
|
||||
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 捕获:失败打印 `校验失败: <异常信息>`,成功打印 `注册成功,欢迎 <username>!`
|
||||
|
||||
**进阶(加分)**:把规则本身做成数据——写一个通用函数
|
||||
`validate(data, rules)`,其中 `rules` 是一个字典:
|
||||
`{字段名: (校验函数, 错误信息)}`,返回所有不满足的字段的错误列表。
|
||||
(提示:校验函数就是你平时写的普通函数或 lambda。)
|
||||
这是后端参数校验框架(比如 DRF 的 Serializer)的雏形,能想出来说明你已经开始有"抽象"的意识了。
|
||||
|
||||
**出题意图**:Django forms / serializers 的校验、Celery 任务的失败处理,
|
||||
核心模式都是"**校验 → 抛异常 → 上层捕获处理**"。
|
||||
今天的题目就是把这个模式焊死在你的肌肉记忆里。
|
||||
|
||||
---
|
||||
|
||||
## 完成标准
|
||||
|
||||
- [ ] 题目一能打印出完整的调用日志(含进阶则 +1)
|
||||
- [ ] 题目二能对"缺字段 / 格式错 / 年龄越界"三种情况分别报错(含进阶则 +1)
|
||||
|
||||
完成后打开 `参考解答.py` 对照。建议看完后关掉文件,自己再默写一遍。
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Day_3 参考解答
|
||||
=============
|
||||
主题:函数与异常。
|
||||
建议:先自己写,写不出来再看;看完后关掉文件自己默写一遍。
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
# ============================================================
|
||||
# 题目一:万能日志记录器
|
||||
# ============================================================
|
||||
|
||||
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: 年龄非法']
|
||||
Reference in New Issue
Block a user