🎯 课程目标

从 Python 零基础到能独立开发 FastAPI 后端服务,具备完整的后端工程能力。

👥 适合谁

有 1 年以上 JavaScript/TypeScript 经验的前端工程师,想向全栈/后端方向发展。

⏱️ 学习时长

认真学完约需 4-6 周,每天 2 小时。完成所有练习题和项目任务是关键。

📍 课程地图(32 节)

阶段内容关键产出
总览(3节)课程地图、思维迁移、环境搭建Python 3.12 环境就绪
语言基础(8节)变量/字符串/控制流/推导式/解包/函数/模块能写 100 行 Python 小脚本
数据结构(4节)list/dict/set/tuple 深度掌握熟练选用合适容器
面向对象(4节)类/继承/魔法方法/dataclass能写面向对象 Python 代码
工程能力(4节)异常/文件/类型/测试代码健壮性达到生产标准
Web 开发(5节)HTTP/FastAPI 入门与进阶/Pydantic/接口设计能独立开发 REST API
实战(4节)项目结构/后端地图/项目路线/考试完整后端项目上线

🔑 前端工程师的核心优势

已有优势:异步思维

你已经理解了回调、Promise、async/await 的异步模型,Python 的 asyncio 对你来说不会陌生。

已有优势:HTTP 协议

你知道 GET/POST、状态码、请求头,学 FastAPI 时只需要学"如何在服务端处理这些"。

已有优势:TypeScript 类型思维

Python 的类型提示系统和 TypeScript 非常相似,你已经理解了为什么要写类型。

需要培养:后端思维

从"渲染 UI"切换到"处理业务逻辑、管理数据持久化",这是最核心的思维转变。

💡 学习建议:每学一个新概念,先问自己"这在 JS/TS 里对应什么?"。大部分时候都有直接对应,Python 不难,只是换了写法。
返回总入口

🔄 核心思维差异对比

维度前端思维(JS/TS)后端思维(Python)
核心关注点用户界面、交互体验、渲染性能业务逻辑、数据处理、系统可靠性
数据生命周期临时状态(刷新即消失)持久化数据(写入数据库长期保存)
错误处理给用户提示、降级展示记录日志、回滚事务、保障数据一致性
并发模型单线程事件循环,异步 I/O多进程/多线程/异步,可真正并行
安全边界输入来自用户,相对可信所有输入都不可信,必须严格校验
性能瓶颈渲染、重排、网络请求数据库查询、I/O、内存占用
部署关注CDN、构建产物、静态资源服务器资源、进程管理、健康检查

🧱 最重要的三个思维切换

① 从"状态"到"数据库"

前端用 useState/Redux 管理状态,后端所有状态最终要落到数据库。想清楚"这个数据要持久化吗"是第一步。

② 从"UI 驱动"到"业务逻辑驱动"

前端代码服务于"页面显示",后端代码服务于"业务规则"。例如"用户余额不足不能下单"是后端逻辑,不是前端判断。

③ 从"客户端"到"服务端"

前端代码跑在用户浏览器,后端代码跑在你的服务器。用户看不到后端代码,但一切敏感逻辑都必须在服务端完成。

📊 技术概念对照表

前端 JS/TS 概念Python 后端对应概念关键差异
package.jsonpyproject.toml / requirements.txt依赖管理文件
npm installpip install包安装命令
node_modules/venv/ 虚拟环境依赖隔离方式不同
express / koaFastAPI / Flask / DjangoWeb 框架
interface / typeTypeVar / dataclass / Pydantic类型定义方式
async/awaitasync/await(相同语法!)Python 3.5+ 原生支持
try/catchtry/except关键字不同,逻辑相同
console.logprint() / logging生产环境用 logging
.env 文件python-dotenv / pydantic-settings环境变量管理
Jest / Vitestpytest测试框架
⚠️ 常见误区:很多前端同学学后端时,想把业务逻辑写在前端,用 API 只做数据传递。这是错误的——凡是涉及安全、权限、数据校验、业务规则的逻辑,都必须在后端完成。

📋 速查:前后端思维差异一览

思维维度前端后端
输入可信度相对可信永远不可信,必须校验
数据持久性临时(内存/localStorage)永久(数据库)
错误暴露可以展示给用户内部细节不能泄漏
代码位置客户端(用户可见)服务端(用户不可见)
并发处理事件循环(单线程)多进程/线程/协程

🔄 对比:Node.js vs Python 的环境管理

工具职责Node.js 生态Python 生态
版本管理nvmpyenv
包管理器npm / yarn / pnpmpip / uv
依赖隔离node_modules(项目内)venv(虚拟环境)
依赖文件package.jsonpyproject.toml / requirements.txt
运行脚本npm run devpython main.py / uvicorn
现代工具链vite / turbouv(极速包管理)

🚀 推荐安装方式(macOS)

# 步骤 1:安装 pyenv(版本管理,类似 nvm) brew install pyenv # 步骤 2:配置 shell(zsh) echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc echo 'eval "$(pyenv init -)"' >> ~/.zshrc source ~/.zshrc # 步骤 3:安装 Python 3.12 pyenv install 3.12.3 pyenv global 3.12.3 # 验证安装 python --version # Python 3.12.3 pip --version # pip 24.x

📦 虚拟环境 venv(类比 node_modules)

# 每个项目都应该有独立的虚拟环境 # 创建虚拟环境(在项目根目录执行) python -m venv venv # 激活虚拟环境(必须!) source venv/bin/activate # macOS/Linux # venv\Scripts\activate # Windows # 激活后,命令行前缀会变成 (venv) # (venv) $ python --version # 安装依赖 pip install fastapi uvicorn pydantic # 保存依赖清单 pip freeze > requirements.txt # 退出虚拟环境 deactivate

⚡ uv:现代 Python 包管理器(推荐)

# uv 是用 Rust 写的超快包管理器,速度比 pip 快 10-100 倍 # 安装 uv curl -LsSf https://astral.sh/uv/install.sh | sh # 创建新项目 uv init my-project cd my-project # 添加依赖 uv add fastapi uv add uvicorn uv add pydantic # 运行脚本 uv run python main.py # 查看依赖 cat pyproject.toml

🔧 IDE 推荐配置(VS Code)

# 推荐安装的 VS Code 扩展 # 1. Python(微软官方) # 2. Pylance(智能补全) # 3. Python Debugger # 4. Ruff(代码检查+格式化,类比 ESLint + Prettier) # 安装 Ruff(代码格式化工具) pip install ruff # 检查代码 ruff check . # 自动修复 ruff check --fix . # 格式化代码 ruff format .

✅ 验证环境:跑一个 FastAPI Hello World

# 安装 FastAPI pip install fastapi "uvicorn[standard]" # 创建 main.py # from fastapi import FastAPI # app = FastAPI() # @app.get("/") # def root(): # return {"message": "Hello from Python!"} # 启动服务 uvicorn main:app --reload # 访问:http://localhost:8000 # 文档:http://localhost:8000/docs
💡 重要习惯:每个 Python 项目都必须有独立的虚拟环境,就像每个 Node 项目有自己的 node_modules。永远不要在全局 Python 环境里装项目依赖。

📋 速查:常用命令一览

操作命令
查看 Python 版本python --version
创建虚拟环境python -m venv venv
激活虚拟环境source venv/bin/activate
安装包pip install package-name
导出依赖pip freeze > requirements.txt
安装所有依赖pip install -r requirements.txt
查看已安装包pip list
格式化代码ruff format .
代码检查ruff check .
运行 FastAPIuvicorn main:app --reload
✏️ 填空题:环境命令练习
创建名为 venv 的虚拟环境: -m venv venv 激活虚拟环境(macOS):source venv//activate 安装 fastapi 包: install fastapi

🔄 JS/TS vs Python 类型系统对比

概念JavaScript / TypeScriptPython
类型声明let x: number = 42x: int = 42
动态/静态JS 动态,TS 静态(编译时)运行时动态,类型提示仅辅助
字符串stringstr
数字number(统一)intfloat(区分整数/浮点)
布尔booleanbool
空值null / undefinedNone(只有一个空值!)
可选类型string | nullOptional[str]str | None
联合类型string | numberUnion[str, int]str | int
常量const全大写命名约定(无语言级 const)
类型检查tsc(编译时)mypy / pyright(外部工具)

📌 基础类型详解

# ===== Python 基础数据类型 ===== # 整数(int)—— 没有大小限制!不像 JS 的 Number.MAX_SAFE_INTEGER age: int = 28 big_number: int = 10 ** 100 # Python 可以处理任意大整数 # 浮点数(float)—— 等价于 JS 的 number price: float = 19.99 pi: float = 3.14159 # 布尔(bool)—— 注意首字母大写 True/False is_active: bool = True is_admin: bool = False # 字符串(str)—— 单引号双引号都行,无区别 name: str = "Alice" greeting: str = 'Hello' # None —— 相当于 JS 的 null,Python 没有 undefined result: None = None # 类型查看 print(type(age)) # <class 'int'> print(type(name)) # <class 'str'> print(type(is_active)) # <class 'bool'> print(type(None)) # <class 'NoneType'> # 类型判断 print(isinstance(age, int)) # True print(isinstance(name, str)) # True print(isinstance(42, (int, float))) # True(检查多个类型)

🏷️ 类型提示语法

# ===== Python 类型提示(Type Hints) ===== # 类比:TypeScript 的类型注解 # 变量类型提示 username: str = "alice" age: int = 28 scores: list[float] = [95.5, 87.3, 92.0] config: dict[str, str] = {"theme": "dark", "lang": "zh"} # 函数类型提示(最常用!) def greet(name: str) -> str: """向用户打招呼""" return f"Hello, {name}!" # 可选参数(值可以为 None) from typing import Optional def find_user(user_id: int) -> Optional[str]: """查找用户,找不到返回 None""" users = {1: "Alice", 2: "Bob"} return users.get(user_id) # 可能返回 str 也可能返回 None # Python 3.10+ 推荐用 | 语法(更简洁) def find_user_v2(user_id: int) -> str | None: users = {1: "Alice", 2: "Bob"} return users.get(user_id) # 联合类型 def process(value: int | str) -> str: return str(value)

📝 命名规范

Python 命名(snake_case)

user_name, is_active, get_user_by_id, MAX_RETRIES

变量和函数:snake_case

类名:PascalCase

常量:UPPER_SNAKE_CASE

JS/TS 命名(camelCase)

userName, isActive, getUserById, MAX_RETRIES

变量和函数:camelCase

类名:PascalCase

常量:UPPER_SNAKE_CASE

⚠️ 常见误区:Python 的类型提示不会在运行时强制检查!x: int = "hello" 不会报错。类型提示只是给 IDE 和 mypy 用的,类似 JSDoc 而非 TypeScript。如果你需要运行时校验,用 Pydantic。

📋 速查:Python 基础类型 + 类型提示

类型PythonTypeScript 对应示例
整数intnumberage: int = 28
浮点floatnumberpi: float = 3.14
字符串strstringname: str = "Bob"
布尔boolbooleanok: bool = True
空值Nonenullx: None = None
列表list[int]number[]nums: list[int] = [1,2]
字典dict[str, int]Record<string,number>d: dict[str,int] = {}
可选str | Nonestring | nullx: str | None = None
联合int | strnumber | stringv: int | str = 1
任意Anyanyx: Any = ...
✏️ 填空:类型提示练习
def calculate_total(price: , quantity: ) -> : return price * quantity # Python 的空值关键字是(首字母大写) empty_value =
🧠 小测验:Python 类型系统

以下哪个说法是正确的?

🔄 JS vs Python 字符串对比

操作JavaScriptPython
模板字符串`Hello ${name}`f"Hello {name}"
拼接"a" + "b""a" + "b""".join([...])
分割str.split(",")str.split(",")
去空格str.trim()str.strip()
大写str.toUpperCase()str.upper()
包含str.includes("x")"x" in str
替换str.replace(a, b)str.replace(a, b)
长度str.lengthlen(str)
索引str[0]str[0]
切片str.slice(1, 3)str[1:3]

🎯 f-string 完整用法

# ===== f-string:Python 最强模板字符串 ===== name = "Alice" age = 28 price = 19.99 # 基础插值(类似 JS 的 ${xxx}) print(f"Hello, {name}!") # Hello, Alice! print(f"{name} is {age} years old") # Alice is 28 years old # 表达式求值 print(f"2 + 3 = {2 + 3}") # 2 + 3 = 5 print(f"大写: {name.upper()}") # 大写: ALICE # 格式化数字 print(f"价格: {price:.2f}") # 价格: 19.99(保留 2 位小数) print(f"百分比: {0.856:.1%}") # 百分比: 85.6% print(f"千分位: {1234567:,}") # 千分位: 1,234,567 print(f"十六进制: {255:#x}") # 十六进制: 0xff # 对齐(左 / 右 / 居中) print(f"{'left':<10}|") # left |(左对齐,宽度10) print(f"{'right':>10}|") # right|(右对齐,宽度10) print(f"{'center':^10}|") # center |(居中,宽度10) # 填充字符 print(f"{'OK':*^10}") # ****OK**** # 调试用法(Python 3.8+) x = 42 print(f"{x = }") # x = 42(自带变量名!)

📚 20+ 常用字符串方法详解

text = " Hello, World! " # === 大小写转换 === print(text.upper()) # " HELLO, WORLD! " print(text.lower()) # " hello, world! " print(text.title()) # " Hello, World! " print("hello".capitalize()) # "Hello" print("Hello".swapcase()) # "hELLO" # === 去空格/字符 === print(text.strip()) # "Hello, World!"(去两端空格) print(text.lstrip()) # "Hello, World! "(去左侧) print(text.rstrip()) # " Hello, World!"(去右侧) print("***OK***".strip("*")) # "OK"(去指定字符) # === 查找与替换 === s = "Hello, World!" print(s.find("World")) # 7(返回索引,找不到返回 -1) print(s.index("World")) # 7(找不到抛异常) print(s.count("l")) # 3(计数出现次数) print(s.replace("World", "Python")) # "Hello, Python!" # === 开头/结尾判断 === print(s.startswith("Hello")) # True print(s.endswith("!")) # True # === 分割与合并 === csv = "apple,banana,cherry" parts = csv.split(",") # ["apple", "banana", "cherry"] print(",".join(parts)) # "apple,banana,cherry" print(" | ".join(parts)) # "apple | banana | cherry" # === 分区 === email = "user@example.com" name, sep, domain = email.partition("@") print(f"用户: {name}, 域名: {domain}") # 用户: user, 域名: example.com # === 填充与对齐 === print("42".zfill(5)) # "00042"(零填充) print("Hi".center(10, "-")) # "----Hi----" print("Hi".ljust(10, ".")) # "Hi........" print("Hi".rjust(10, ".")) # "........Hi" # === 判断类方法 === print("abc".isalpha()) # True(全是字母) print("123".isdigit()) # True(全是数字) print("abc123".isalnum()) # True(字母+数字) print(" ".isspace()) # True(全是空白) # === Python 3.9+ 新增 === print("HelloWorld".removeprefix("Hello")) # "World" print("test_file.py".removesuffix(".py")) # "test_file"

📝 多行字符串与原始字符串

# 多行字符串(三引号) poem = """ 静夜思 床前明月光, 疑是地上霜。 """ print(poem) # 原始字符串 r""(不转义,常用于正则和路径) path = r"C:\Users\name\docs" # 不会把 \n 当换行符 regex = r"\d+\.\d+" # 正则表达式不用双重转义 # 字节字符串 b""(用于网络传输、文件读写) data = b"Hello" print(type(data)) # <class 'bytes'> # 编码/解码(str <-> bytes) text = "你好" encoded = text.encode("utf-8") # str -> bytes decoded = encoded.decode("utf-8") # bytes -> str
⚠️ 常见误区:Python 字符串是不可变的(和 JS 一样),所有字符串方法都返回新字符串,不会修改原字符串。s.upper() 不改变 s,需要 s = s.upper()

📋 速查:字符串方法大全

方法作用示例
upper()转大写"hi".upper() → "HI"
lower()转小写"HI".lower() → "hi"
title()每词首字母大写"hello world".title()
capitalize()首字母大写"hello".capitalize()
strip()去两端空白" hi ".strip()
lstrip()去左侧空白" hi".lstrip()
rstrip()去右侧空白"hi ".rstrip()
split(sep)按分隔符分割"a,b".split(",") → ["a","b"]
join(list)合并列表为字符串",".join(["a","b"])
replace(a,b)替换子串"hi".replace("h","H")
find(sub)查找索引(-1表示没找到)"hello".find("ll") → 2
count(sub)计数子串出现次数"hello".count("l") → 2
startswith(s)是否以 s 开头"hello".startswith("he")
endswith(s)是否以 s 结尾"hello".endswith("lo")
zfill(n)零填充到 n 位"42".zfill(5) → "00042"
center(n)居中"hi".center(10)
partition(sep)按首次出现分三部分"a@b".partition("@")
encode()编码为 bytes"hi".encode("utf-8")
removeprefix()去前缀(3.9+)"test_x".removeprefix("test_")
removesuffix()去后缀(3.9+)"file.py".removesuffix(".py")
isdigit()是否全数字"123".isdigit() → True
isalpha()是否全字母"abc".isalpha() → True
✏️ 填空:字符串方法练习
# 用 f-string 保留两位小数 price = 19.999 formatted = f"总价: {price:}" # "总价: 20.00" # 把 "hello world" 每个单词首字母大写 result = "hello world".() # "Hello World" # 在字符串 "user@email.com" 中提取 "@" 之前的部分 name, _, domain = "user@email.com".("@")
🎯 任务:字符串格式化练习
写一个函数 format_receipt(items),接受一个商品列表(每项含 name 和 price),输出格式化的收据字符串,包含商品名(左对齐20字符)和价格(右对齐10字符,保留2位小数),以及总价。
查看参考思路
def format_receipt(items: list[dict]) -> str: lines = ["=" * 32] lines.append(f"{'商品':<18}{'价格':>12}") lines.append("-" * 32) total = 0.0 for item in items: name = item["name"] price = item["price"] total += price lines.append(f"{name:<20}{price:>10.2f}") lines.append("-" * 32) lines.append(f"{'合计':<20}{total:>10.2f}") lines.append("=" * 32) return "\n".join(lines) items = [ {"name": "Python入门", "price": 59.90}, {"name": "FastAPI实战", "price": 79.00}, {"name": "数据库设计", "price": 45.50}, ] print(format_receipt(items))
🧠 小测验

Python 中 "hello".find("xyz") 返回什么?

🔄 JS vs Python 控制流对比

结构JavaScriptPython
条件判断if (x > 0) { }if x > 0:
否则如果else ifelif
for 循环for (let i=0; i<n; i++)for i in range(n):
遍历数组for (const x of arr)for x in arr:
三元x ? a : ba if x else b
switchswitch(x) { case: }match x:(3.10+)
逻辑运算&& || !and or not

🔀 if / elif / else

# ===== 条件判断 ===== score = 85 # 基础 if/elif/else if score >= 90: grade = "A" elif score >= 80: grade = "B" elif score >= 70: grade = "C" else: grade = "D" print(f"成绩: {grade}") # 成绩: B # 三元表达式(Python 的写法和 JS 不同!) # JS: const msg = age >= 18 ? "成年" : "未成年" # Python: age = 20 msg = "成年" if age >= 18 else "未成年" print(msg) # 成年 # 逻辑运算符(用英文单词!不是 && || !) is_admin = True is_active = True if is_admin and is_active: print("有权限") has_permission = not is_admin or is_active # True # 链式比较(Python 独有,非常优雅!) x = 15 if 10 <= x <= 20: print("x 在 10 到 20 之间") # JS 需要 x >= 10 && x <= 20 # 真值判断(Python 的 falsy 值) # False, 0, 0.0, "", [], {}, set(), None 都是 falsy if not []: print("空列表是 falsy")

🔁 for 循环(Python 最强大的循环)

# ===== 遍历列表 ===== fruits = ["apple", "banana", "cherry"] for fruit in fruits: print(fruit) # ===== range() 生成数字序列 ===== for i in range(5): # 0,1,2,3,4 print(i) for i in range(1, 6): # 1,2,3,4,5(左闭右开) print(i) for i in range(0, 10, 2): # 0,2,4,6,8(步长为2) print(i) # ===== enumerate():同时获取索引和值(最常用!)===== # JS: fruits.forEach((fruit, index) => ...) for index, fruit in enumerate(fruits): print(f"{index}: {fruit}") # 0: apple # 1: banana # 2: cherry # ===== zip():同时遍历多个列表 ===== names = ["Alice", "Bob", "Charlie"] ages = [25, 30, 35] for name, age in zip(names, ages): print(f"{name} 今年 {age} 岁") # ===== 遍历字典 ===== user = {"name": "Alice", "age": 25, "role": "admin"} for key in user: print(key) for key, value in user.items(): print(f"{key}: {value}") # ===== for-else(Python 独有!循环正常结束时执行 else)===== for n in range(2, 10): for x in range(2, n): if n % x == 0: break else: # 如果没有 break,说明是质数 print(f"{n} 是质数")

🔄 while 循环

# while 循环 count = 0 while count < 5: print(count) count += 1 # Python 没有 ++ 运算符! # break 和 continue for i in range(10): if i == 3: continue # 跳过 3 if i == 7: break # 到 7 停止 print(i) # 0, 1, 2, 4, 5, 6

🎯 match-case(Python 3.10+,类似 switch)

# match-case 比 switch 更强大,支持模式匹配 status = 404 match status: case 200: print("OK") case 301 | 302: # 多个值匹配 print("重定向") case 404: print("未找到") case 500: print("服务器错误") case _: # 默认分支(类似 default) print(f"其他状态: {status}") # 解构匹配(match 最强大的功能!) point = (3, 4) match point: case (0, 0): print("原点") case (x, 0): print(f"x轴上: {x}") case (0, y): print(f"y轴上: {y}") case (x, y): print(f"坐标: ({x}, {y})") # 坐标: (3, 4)
⚠️ 常见误区:① Python 没有 ++-- 运算符,用 += 1 代替。② for-else 的 else 是"循环正常结束时执行"而不是"循环不执行时执行"。③ Python 用 and/or/not 而不是 &&/||/!

📋 速查:控制流语法一览

结构语法说明
ifif cond:条件为真时执行
elifelif cond:否则如果
elseelse:否则
for-infor x in iterable:遍历可迭代对象
rangerange(start,stop,step)生成数字序列
enumerateenumerate(iterable)同时获取索引和值
zipzip(a, b)同时遍历多个序列
whilewhile cond:条件循环
breakbreak退出循环
continuecontinue跳过本次循环
for-elsefor...else:循环正常完成执行 else
matchmatch value:模式匹配(3.10+)
三元a if cond else b条件表达式
链式比较a <= x <= bPython 独有优雅写法
✏️ 填空:控制流练习
# 同时获取列表的索引和值 for index, value in (fruits): print(f"{index}: {value}") # Python 的三元表达式 result = "偶数" x % 2 == 0 "奇数"
🧠 小测验

for i in range(1, 10, 3): 会产生哪些数字?

🔄 JS 的 map/filter vs Python 推导式

操作JavaScriptPython 推导式
映射arr.map(x => x * 2)[x * 2 for x in arr]
过滤arr.filter(x => x > 0)[x for x in arr if x > 0]
映射+过滤arr.filter(...).map(...)[x*2 for x in arr if x>0]

📋 列表推导式

# ===== 列表推导式(List Comprehension)===== # 基础语法:[表达式 for 变量 in 可迭代对象] numbers = [1, 2, 3, 4, 5] squares = [n ** 2 for n in numbers] # [1, 4, 9, 16, 25] doubled = [n * 2 for n in numbers] # [2, 4, 6, 8, 10] # 带条件过滤:[表达式 for 变量 in 可迭代对象 if 条件] evens = [n for n in numbers if n % 2 == 0] # [2, 4] big = [n for n in numbers if n > 3] # [4, 5] # 带条件表达式(类似 JS 三元) labels = ["偶" if n % 2 == 0 else "奇" for n in numbers] # ["奇", "偶", "奇", "偶", "奇"] # 字符串操作 names = ["alice", "bob", "charlie"] upper_names = [name.upper() for name in names] # ["ALICE", "BOB", "CHARLIE"] long_names = [name for name in names if len(name) > 3] # ["alice", "charlie"] # 嵌套循环推导式 matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] flat = [x for row in matrix for x in row] # [1,2,3,4,5,6,7,8,9] # 等价于: # for row in matrix: # for x in row: # flat.append(x)

📖 字典推导式和集合推导式

# ===== 字典推导式 ===== names = ["alice", "bob", "charlie"] name_lengths = {name: len(name) for name in names} # {"alice": 5, "bob": 3, "charlie": 7} # 反转字典 original = {"a": 1, "b": 2, "c": 3} reversed_dict = {v: k for k, v in original.items()} # {1: "a", 2: "b", 3: "c"} # 过滤字典 scores = {"Alice": 95, "Bob": 60, "Charlie": 85} passed = {name: score for name, score in scores.items() if score >= 70} # {"Alice": 95, "Charlie": 85} # ===== 集合推导式 ===== words = ["hello", "world", "hello", "python"] unique_lengths = {len(w) for w in words} # {5, 6}

⚡ 生成器表达式

# ===== 生成器表达式 vs 列表推导式 ===== # 列表推导式 [] —— 立即计算,全部存入内存 squares_list = [n ** 2 for n in range(1000000)] # 占大量内存 # 生成器表达式 () —— 惰性计算,只在需要时计算下一个值 squares_gen = (n ** 2 for n in range(1000000)) # 几乎不占内存 # 生成器只能遍历一次 for val in squares_gen: if val > 100: break # 常见用法:配合 sum/max/min/any/all 使用 total = sum(n ** 2 for n in range(100)) # 不需要额外的括号 biggest = max(len(name) for name in names) has_admin = any(u["role"] == "admin" for u in users)
💡 什么时候用哪个?数据量小(<1000)或需要多次访问 → 列表推导式。数据量大或只遍历一次 → 生成器表达式。需要索引随机访问 → 列表推导式。
⚠️ 常见误区:推导式不是越复杂越好。如果嵌套超过 2 层或逻辑复杂,就用普通 for 循环。代码可读性 > 简洁性。

📋 速查:推导式语法大全

类型语法示例
列表[expr for x in iter][x*2 for x in range(5)]
列表+过滤[expr for x in iter if cond][x for x in arr if x>0]
列表+条件表达式[a if cond else b for x in iter]["偶" if x%2==0 else "奇" for x in arr]
字典{k:v for k,v in iter}{n:n**2 for n in range(5)}
集合{expr for x in iter}{len(w) for w in words}
生成器(expr for x in iter)sum(x**2 for x in range(100))
嵌套[expr for a in A for b in B][x for row in matrix for x in row]
✏️ 填空:推导式练习
# 用列表推导式获取 1-10 中的偶数 evens = [n for n in range(1, 11) ] # 用字典推导式构建 {名字: 长度} 映射 names = ["alice", "bob"] lengths = { for name in names}

🔄 JS 解构 vs Python 解包

操作JavaScriptPython
数组/列表解构const [a, b] = [1, 2]a, b = [1, 2]
跳过元素const [a, , c] = arra, _, c = arr
剩余参数const [a, ...rest] = arra, *rest = arr
对象解构const {name, age} = objPython 没有对象解构语法
切片arr.slice(1, 3)arr[1:3]
展开[...a, ...b][*a, *b]
字典展开{...a, ...b}{**a, **b}

📤 序列解包

# ===== 基础解包 ===== a, b, c = [1, 2, 3] print(a, b, c) # 1 2 3 # 交换变量(Python 最优雅的写法!) x, y = 10, 20 x, y = y, x # 一行交换!JS 需要 [x, y] = [y, x] print(x, y) # 20 10 # 用 _ 忽略不需要的值 first, _, last = "Alice Bob Charlie".split() print(first, last) # Alice Charlie # ===== 星号解包(*rest)===== first, *rest = [1, 2, 3, 4, 5] print(first) # 1 print(rest) # [2, 3, 4, 5] first, *middle, last = [1, 2, 3, 4, 5] print(first) # 1 print(middle) # [2, 3, 4] print(last) # 5 # ===== 嵌套解包 ===== (a, b), (c, d) = [1, 2], [3, 4] print(a, b, c, d) # 1 2 3 4 # 来自元组列表 points = [(1, 2), (3, 4), (5, 6)] for x, y in points: print(f"坐标: ({x}, {y})") # ===== 字典展开合并 ===== defaults = {"theme": "light", "lang": "en"} overrides = {"lang": "zh", "debug": True} config = {**defaults, **overrides} # {"theme": "light", "lang": "zh", "debug": True} # 后面的值覆盖前面的,类似 JS 的 {...defaults, ...overrides}

🔪 切片操作

# ===== 切片语法:list[start:stop:step] ===== nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 基础切片(左闭右开,和 JS 的 slice 一样) print(nums[2:5]) # [2, 3, 4] print(nums[:3]) # [0, 1, 2](从头开始) print(nums[7:]) # [7, 8, 9](到末尾) print(nums[:]) # 浅复制整个列表 # 步长切片 print(nums[::2]) # [0, 2, 4, 6, 8](每隔一个取) print(nums[1::2]) # [1, 3, 5, 7, 9](奇数索引) # ===== 负数索引(从末尾算起)===== print(nums[-1]) # 9(最后一个) print(nums[-3:]) # [7, 8, 9](最后三个) print(nums[:-2]) # [0,1,2,3,4,5,6,7](去掉最后两个) # ===== 反转 ===== print(nums[::-1]) # [9,8,7,6,5,4,3,2,1,0](反转列表!) # ===== 字符串切片(字符串也是序列!)===== text = "Hello, Python!" print(text[7:13]) # "Python" print(text[::-1]) # "!nohtyP ,olleH" # ===== 切片赋值(修改列表片段)===== nums = [0, 1, 2, 3, 4] nums[1:3] = [10, 20] # [0, 10, 20, 3, 4] nums[1:3] = [10, 20, 30] # 切片赋值可以改变长度!
⚠️ 常见误区:切片是左闭右开的——nums[1:3] 包含索引 1 和 2,不包含 3。这和 JS 的 slice 完全一致,但初学者容易忘记。

📋 速查:解包与切片语法

操作语法示例
基础解包a, b = [1, 2]个数必须匹配
忽略a, _ = [1, 2]用 _ 表示不需要
剩余收集a, *rest = listrest 是列表
交换a, b = b, a一行搞定
列表展开[*a, *b]合并列表
字典展开{**a, **b}合并字典
基础切片list[start:stop]左闭右开
步长切片list[start:stop:step]每隔 step 取一个
负索引list[-1]最后一个元素
反转list[::-1]反转序列
浅复制list[:]复制整个列表
✏️ 填空:切片练习
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 获取最后三个元素 last_three = nums[:] # 反转列表 reversed_nums = nums[::] # 每隔一个取一个(偶数索引) every_other = nums[::]

🔄 JS vs Python 函数对比

概念JavaScriptPython
定义函数function greet(name) {}def greet(name):
箭头函数const f = (x) => x * 2f = lambda x: x * 2
默认参数function f(x = 10)def f(x=10):
剩余参数function f(...args)def f(*args):
返回值return valuereturn value
多返回值不原生支持(返回对象)return a, b(返回元组)
文档注释/** JSDoc */"""docstring"""

📌 函数定义与参数

# ===== 基础函数定义 ===== def greet(name: str) -> str: """向用户打招呼(这是 docstring 文档字符串)""" return f"Hello, {name}!" print(greet("Alice")) # Hello, Alice! # ===== 位置参数 vs 关键字参数 ===== def create_user(name: str, age: int, role: str = "member") -> dict: """创建用户字典""" return {"name": name, "age": age, "role": role} # 位置参数传递 user1 = create_user("Alice", 28) # 关键字参数传递(可以不按顺序!) user2 = create_user(age=30, name="Bob", role="admin") # 混合使用(位置参数必须在前面) user3 = create_user("Charlie", 25, role="editor") # ===== 多返回值(实际是返回元组)===== def divide(a: float, b: float) -> tuple[float, float]: """同时返回商和余数""" quotient = a // b remainder = a % b return quotient, remainder # 返回元组 q, r = divide(17, 5) # 解包接收 print(f"商: {q}, 余数: {r}") # 商: 3.0, 余数: 2.0 # ===== 强制关键字参数(* 分隔符后面的参数必须用关键字传递)===== def connect(host: str, port: int, *, timeout: int = 30, ssl: bool = False): """timeout 和 ssl 必须用关键字传递""" print(f"连接 {host}:{port}, timeout={timeout}, ssl={ssl}") connect("localhost", 5432, timeout=10, ssl=True) # ✅ # connect("localhost", 5432, 10, True) # ❌ 报错!

📝 Docstring(文档字符串)

def calculate_bmi(weight: float, height: float) -> float: """ 计算 BMI 指数。 Args: weight: 体重,单位 kg height: 身高,单位 m Returns: BMI 指数值 Raises: ValueError: 如果体重或身高为负数 Examples: >>> calculate_bmi(70, 1.75) 22.86 """ if weight <= 0 or height <= 0: raise ValueError("体重和身高必须为正数") return round(weight / height ** 2, 2) # 查看文档 help(calculate_bmi) print(calculate_bmi.__doc__)

🔬 作用域(LEGB 规则)

# Python 作用域查找顺序:LEGB # L - Local 本地作用域(函数内部) # E - Enclosing 外层函数作用域(闭包) # G - Global 全局作用域(模块级别) # B - Built-in 内置作用域(print, len 等) x = "全局" # Global def outer(): x = "外层" # Enclosing def inner(): x = "本地" # Local print(x) # "本地" inner() print(x) # "外层" outer() print(x) # "全局" # 注意:函数内部可以读取外层变量,但要修改需要用 global/nonlocal counter = 0 def increment(): global counter # 声明要修改全局变量 counter += 1 def make_counter(): count = 0 def increment(): nonlocal count # 声明要修改外层函数变量 count += 1 return count return increment
⚠️ 常见误区:① Python 的默认参数只在函数定义时计算一次!def f(items=[]) 是经典 bug,应该用 def f(items=None)。② 没有 return 语句的函数返回 None。

📋 速查:函数语法

语法说明示例
def f(x):定义函数def add(a, b): return a+b
def f(x=10):默认参数def f(n=0):
def f(*args):可变位置参数args 是元组
def f(**kwargs):可变关键字参数kwargs 是字典
def f(*, key):强制关键字参数* 后面必须用关键字
return a, b多返回值返回元组,可解包
"""docstring"""文档字符串函数第一行字符串
-> Type返回值类型提示def f() -> int:
global x引用全局变量修改全局变量时使用
nonlocal x引用外层变量闭包中修改外层变量
✏️ 填空:函数练习
# 定义一个带默认参数的函数 greet(name: str, greeting: str = "Hello") -> str: f"{greeting}, {name}!" # Python 作用域查找顺序 # L - Local, E - Enclosing, G - , B - Built-in
🧠 小测验

以下代码会输出什么?
def f(items=[]): items.append(1); return items
print(f()); print(f())

🔄 JS vs Python 高级函数特性对比

概念JavaScriptPython
函数作为值const fn = addfn = add
匿名函数(x) => x * 2lambda x: x * 2
闭包函数引用外层变量完全相同的概念
装饰器无原生语法,需手动包装@decorator 语法糖
map/filterarr.map(fn)map(fn, arr)(推荐用推导式)

🔧 闭包(Closure)

# ===== 闭包:函数 + 它所引用的外层变量 ===== # 和 JS 的闭包概念完全一致 def make_multiplier(factor: int): """返回一个乘法器函数""" def multiply(n: int) -> int: return n * factor # 引用了外层变量 factor return multiply double = make_multiplier(2) triple = make_multiplier(3) print(double(5)) # 10 print(triple(5)) # 15 # 实际应用:计数器 def make_counter(start: int = 0): count = start def increment() -> int: nonlocal count count += 1 return count return increment counter = make_counter() print(counter()) # 1 print(counter()) # 2 print(counter()) # 3

✨ 装饰器(Decorator)—— Python 的杀手级特性

import functools import time # ===== 装饰器 = 高阶函数的语法糖 ===== # JS 中你可能这样写:const wrappedFn = wrapper(originalFn) # Python 用 @decorator 语法更简洁 # 基础装饰器:计时器 def timer(func): """测量函数执行时间的装饰器""" @functools.wraps(func) # 保留原函数的元信息 def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) elapsed = time.time() - start print(f"⏱️ {func.__name__} 执行耗时: {elapsed:.4f}s") return result return wrapper @timer def slow_function(): time.sleep(1) return "done" slow_function() # ⏱️ slow_function 执行耗时: 1.0012s # ===== 带参数的装饰器 ===== def retry(max_attempts: int = 3): """失败重试装饰器""" def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): for attempt in range(1, max_attempts + 1): try: return func(*args, **kwargs) except Exception as e: if attempt == max_attempts: raise print(f"尝试 {attempt} 失败: {e},重试中...") return wrapper return decorator @retry(max_attempts=3) def fetch_data(url: str) -> str: """模拟网络请求""" # ... 可能会失败的操作 return "data" # ===== 常见实用装饰器 ===== def require_auth(func): """权限检查装饰器""" @functools.wraps(func) def wrapper(user, *args, **kwargs): if not user.get("authenticated"): raise PermissionError("未登录") return func(user, *args, **kwargs) return wrapper @require_auth def get_profile(user: dict) -> dict: return {"name": user["name"]}

🔑 lambda 表达式

# lambda 是匿名函数,只能写一个表达式(不能写语句) # 等价于 JS 的箭头函数 (x) => x * 2 square = lambda x: x ** 2 print(square(5)) # 25 # 最常见用法:作为 sort 的 key 函数 users = [ {"name": "Charlie", "age": 35}, {"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}, ] # 按 age 排序 sorted_users = sorted(users, key=lambda u: u["age"]) # 按 name 排序 sorted_by_name = sorted(users, key=lambda u: u["name"])
⚠️ 常见误区:functools.wraps 必须加!否则被装饰的函数会失去原来的名字和文档。② lambda 只能包含一个表达式,不能有赋值或多行语句。如果逻辑复杂,用 def。

📋 速查:装饰器与高阶函数

语法说明示例
lambda x: expr匿名函数lambda x: x * 2
@decorator装饰器语法糖@timer
@deco(args)带参数装饰器@retry(3)
functools.wraps保留被装饰函数信息@functools.wraps(func)
nonlocal闭包修改外层变量nonlocal count
map(fn, iter)映射推荐用推导式代替
filter(fn, iter)过滤推荐用推导式代替
sorted(iter, key=fn)排序sorted(users, key=lambda u: u["age"])
🎯 任务:写一个日志装饰器
写一个 @log_call 装饰器,在函数调用前打印函数名和参数,调用后打印返回值。
查看参考思路
import functools def log_call(func): @functools.wraps(func) def wrapper(*args, **kwargs): args_repr = [repr(a) for a in args] kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()] signature = ", ".join(args_repr + kwargs_repr) print(f"📞 调用 {func.__name__}({signature})") result = func(*args, **kwargs) print(f"✅ {func.__name__} 返回 {result!r}") return result return wrapper @log_call def add(a, b): return a + b add(3, 5) # 📞 调用 add(3, 5) # ✅ add 返回 8

🔄 JS vs Python 模块系统对比

操作JavaScript (ES Modules)Python
导入整个模块import * as utils from './utils'import utils
导入特定成员import { add } from './math'from math import add
别名import { add as plus }from math import add as plus
默认导出export default fnPython 没有默认导出概念
包入口index.js__init__.py

📦 import 的各种写法

# ===== 导入方式 ===== # 1. 导入整个模块 import os print(os.path.exists("/tmp")) # 2. 导入特定成员 from os.path import exists, join print(exists("/tmp")) # 3. 别名导入(长名字时很有用) import pandas as pd import numpy as np from datetime import datetime as dt # 4. 导入模块中的所有(不推荐!会污染命名空间) # from os import * # 别这么做 # 5. 相对导入(在包内部使用) # from . import utils # 同级目录 # from .. import config # 上级目录 # from .models import User # 同级目录下的模块

📁 __init__.py 的作用

# 目录结构: # myapp/ # ├── __init__.py ← 标记这是一个 Python 包 # ├── models.py # ├── services.py # └── utils/ # ├── __init__.py ← 子包也需要 # ├── helpers.py # └── validators.py # __init__.py 可以为空,也可以定义包级别的导出 # myapp/__init__.py # from .models import User # from .services import UserService # 这样外部就可以: # from myapp import User, UserService

🎯 if __name__ == "__main__"

# 每个 .py 文件既可以作为模块被导入,也可以直接运行 # __name__ 的值取决于文件是被导入还是直接运行 # utils.py def add(a: int, b: int) -> int: return a + b def subtract(a: int, b: int) -> int: return a - b # 只在直接运行 python utils.py 时执行,被 import 时不执行 if __name__ == "__main__": # 通常用来放测试代码或命令行入口 print(add(2, 3)) # 5 print(subtract(10, 4)) # 6

📂 推荐项目结构

# ===== 推荐的 Python 后端项目结构 ===== # my-api/ # ├── pyproject.toml ← 项目配置(类比 package.json) # ├── requirements.txt ← 依赖清单 # ├── README.md # ├── .env ← 环境变量(不提交到 git) # ├── .gitignore # ├── app/ ← 应用代码 # │ ├── __init__.py # │ ├── main.py ← FastAPI 入口 # │ ├── config.py ← 配置管理 # │ ├── models/ ← 数据模型 # │ │ ├── __init__.py # │ │ └── user.py # │ ├── services/ ← 业务逻辑 # │ │ ├── __init__.py # │ │ └── user_service.py # │ ├── routers/ ← API 路由 # │ │ ├── __init__.py # │ │ └── user_router.py # │ └── utils/ ← 工具函数 # │ ├── __init__.py # │ └── helpers.py # └── tests/ ← 测试 # ├── __init__.py # ├── test_user_service.py # └── test_user_router.py
⚠️ 常见误区:① 循环导入——A 导入 B,B 又导入 A,会导致 ImportError。解决方法:重新组织代码结构或使用延迟导入。② 不要用 from module import *,它会让命名空间变得不可预测。

📋 速查:import 语法

语法用途示例
import module导入整个模块import os
import module as alias别名导入import pandas as pd
from module import name导入特定成员from os import path
from module import name as alias成员别名from datetime import datetime as dt
from . import module相对导入(同级)包内部使用
from .. import module相对导入(上级)包内部使用
__init__.py包标识文件可为空或定义包导出
if __name__ == "__main__":直接运行入口测试和 CLI 入口
✏️ 填空:模块导入练习
# 从 datetime 模块导入 datetime 类并起别名 from datetime datetime as dt # 判断文件是否被直接运行 if : print("直接运行")

🔄 JS Array vs Python list

操作JavaScriptPython
创建[1, 2, 3][1, 2, 3]
末尾添加arr.push(4)arr.append(4)
合并数组arr.concat(other)arr.extend(other)arr + other
插入arr.splice(i,0,v)arr.insert(i, v)
删除末尾arr.pop()arr.pop()
删除指定值arr.splice(i,1)arr.remove(value)
查找索引arr.indexOf(v)arr.index(v)
排序arr.sort()arr.sort()
反转arr.reverse()arr.reverse()
长度arr.lengthlen(arr)
包含arr.includes(v)v in arr

📚 List 方法完全指南

# ===== 创建 ===== empty = [] nums = [1, 2, 3, 4, 5] mixed = [1, "hello", True, None] # 可以混合类型(但不推荐) from_range = list(range(10)) # [0,1,2,...,9] # ===== 增 ===== fruits = ["apple", "banana"] fruits.append("cherry") # 末尾添加一个:["apple", "banana", "cherry"] fruits.extend(["date", "elderberry"]) # 合并列表 fruits.insert(1, "blueberry") # 在索引 1 插入 # ===== 删 ===== fruits.pop() # 删除并返回最后一个 fruits.pop(0) # 删除并返回索引 0 fruits.remove("banana") # 删除第一个匹配值(没有则报错) fruits.clear() # 清空列表 # ===== 查 ===== nums = [3, 1, 4, 1, 5, 9, 2, 6] print(nums.index(4)) # 2(第一次出现的索引) print(nums.count(1)) # 2(出现次数) print(5 in nums) # True(是否包含) # ===== 排序 ===== # sort() 原地排序(修改原列表) nums.sort() # 升序排列 nums.sort(reverse=True) # 降序排列 # sorted() 返回新列表(不修改原列表——推荐!) original = [3, 1, 4, 1, 5] ascending = sorted(original) # [1, 1, 3, 4, 5] descending = sorted(original, reverse=True) # [5, 4, 3, 1, 1] # 自定义排序 words = ["banana", "apple", "cherry"] by_length = sorted(words, key=len) # 按长度排序 by_last_char = sorted(words, key=lambda w: w[-1]) # 按最后一个字符 # ===== 复制 ===== original = [1, [2, 3], 4] shallow = original.copy() # 浅复制(嵌套对象共享引用) shallow2 = original[:] # 也是浅复制 import copy deep = copy.deepcopy(original) # 深复制(完全独立) # ===== 反转 ===== nums = [1, 2, 3] nums.reverse() # 原地反转 [3, 2, 1] reversed_new = nums[::-1] # 返回新列表(不修改原列表)
⚠️ 常见误区:sort() 返回 None(原地修改),不能写 new_list = old_list.sort()。要用 sorted() 获取新列表。② remove() 只删除第一个匹配项,如果元素不存在会报 ValueError。

📋 速查:List 方法大全

方法作用返回值
append(x)末尾添加None(原地)
extend(iter)合并可迭代对象None(原地)
insert(i, x)在索引 i 插入None(原地)
pop(i=-1)删除并返回指定索引被删除的元素
remove(x)删除第一个匹配项None
clear()清空None
index(x)查找索引int(没有则报错)
count(x)计数int
sort()原地排序None
reverse()原地反转None
copy()浅复制新 list
sorted(list)排序(内置函数)新 list
len(list)长度int
x in list包含判断bool
✏️ 填空
# 在列表末尾添加元素(JS 的 push) fruits = ["apple"] fruits.("banana") # 获取排序后的新列表(不修改原列表) nums = [3, 1, 2] ordered = (nums)

🔄 JS Object vs Python dict

操作JavaScriptPython
创建{ name: "Alice" }{"name": "Alice"}(键必须加引号)
访问obj.name / obj["name"]d["name"](没有点语法)
安全访问obj?.named.get("name")
修改obj.name = "Bob"d["name"] = "Bob"
删除delete obj.namedel d["name"]
合并{...a, ...b}{**a, **b}a | b(3.9+)
遍历键Object.keys(obj)d.keys()
遍历值Object.values(obj)d.values()
遍历键值Object.entries(obj)d.items()

📚 Dict 方法完全指南

# ===== 创建字典 ===== user = {"name": "Alice", "age": 28, "role": "admin"} from_pairs = dict([("a", 1), ("b", 2)]) # 从键值对列表创建 from_keys = dict.fromkeys(["x", "y"], 0) # {"x": 0, "y": 0} # ===== 访问 ===== print(user["name"]) # "Alice"(键不存在会报 KeyError) print(user.get("email")) # None(安全访问,键不存在返回 None) print(user.get("email", "N/A")) # "N/A"(指定默认值) # ===== 增改 ===== user["email"] = "alice@example.com" # 添加/修改 user.update({"age": 29, "city": "Beijing"}) # 批量更新 # ===== 删除 ===== del user["city"] # 删除键(不存在报 KeyError) email = user.pop("email") # 删除并返回值 email = user.pop("email", None) # 安全删除(不存在返回默认值) # ===== setdefault:获取或设置默认值 ===== # 键存在则返回值,不存在则设置默认值并返回 scores = {} scores.setdefault("Alice", []).append(95) scores.setdefault("Alice", []).append(87) print(scores) # {"Alice": [95, 87]} # ===== 遍历 ===== config = {"host": "localhost", "port": 8080, "debug": True} for key in config: print(key) for key, value in config.items(): print(f"{key} = {value}") # ===== 合并字典 ===== defaults = {"theme": "light", "lang": "en"} custom = {"lang": "zh", "debug": True} # 方式 1: 解包 merged = {**defaults, **custom} # 方式 2: | 运算符(Python 3.9+) merged = defaults | custom # 方式 3: update(原地修改) defaults.update(custom)

📦 collections 模块的高级字典

from collections import defaultdict, Counter, OrderedDict # ===== defaultdict:自动初始化默认值 ===== word_count = defaultdict(int) # 默认值为 0 for word in ["apple", "banana", "apple", "cherry", "apple"]: word_count[word] += 1 print(dict(word_count)) # {"apple": 3, "banana": 1, "cherry": 1} groups = defaultdict(list) # 默认值为空列表 for name, dept in [("Alice", "dev"), ("Bob", "dev"), ("Charlie", "ops")]: groups[dept].append(name) print(dict(groups)) # {"dev": ["Alice", "Bob"], "ops": ["Charlie"]} # ===== Counter:计数器 ===== words = ["apple", "banana", "apple", "cherry", "apple", "banana"] counter = Counter(words) print(counter) # Counter({"apple": 3, "banana": 2, "cherry": 1}) print(counter.most_common(2)) # [("apple", 3), ("banana", 2)] print(counter["apple"]) # 3
⚠️ 常见误区:① Python dict 没有 JS 对象的点语法访问,d.name 会报 AttributeError。② d["key"] 在键不存在时报 KeyError,用 d.get("key") 更安全。③ Python 3.7+ dict 保持插入顺序。

📋 速查:Dict 方法大全

方法作用说明
d[key]获取值不存在报 KeyError
d.get(key, default)安全获取不存在返回 default
d[key] = val设置值创建或覆盖
d.update(other)批量更新原地修改
d.pop(key)删除并返回不存在报错
d.setdefault(k,v)获取或设置默认值常用于分组
d.keys()所有键返回视图对象
d.values()所有值返回视图对象
d.items()所有键值对返回视图对象
key in d键是否存在bool
d | other合并(3.9+)返回新 dict
defaultdict(type)带默认值的字典from collections
Counter(iter)计数器from collections
✏️ 填空
# 安全获取字典值(键不存在返回 "unknown") name = user.("name", "unknown") # 遍历字典的键和值 for key, value in config.(): print(f"{key}: {value}")

🔄 JS Set vs Python set

操作JavaScriptPython
创建new Set([1,2,3]){1, 2, 3}
添加set.add(4)s.add(4)
删除set.delete(2)s.discard(2)
包含set.has(1)1 in s
并集手动遍历a | b
交集手动遍历a & b
差集手动遍历a - b

📚 Set 操作完全指南

# ===== 创建集合 ===== colors = {"red", "green", "blue"} from_list = set([1, 2, 2, 3, 3]) # {1, 2, 3}(自动去重) empty_set = set() # 注意:{} 创建的是空字典! # ===== 增删 ===== colors.add("yellow") # 添加元素 colors.discard("red") # 安全删除(不存在不报错) colors.remove("green") # 删除(不存在报 KeyError) # ===== 集合运算(Python 最强大的特性!)===== a = {1, 2, 3, 4, 5} b = {4, 5, 6, 7, 8} # 并集(union) print(a | b) # {1, 2, 3, 4, 5, 6, 7, 8} print(a.union(b)) # 同上 # 交集(intersection) print(a & b) # {4, 5} print(a.intersection(b)) # 同上 # 差集(difference) print(a - b) # {1, 2, 3}(在 a 中但不在 b 中) print(a.difference(b)) # 同上 # 对称差集(只在一个集合中出现的元素) print(a ^ b) # {1, 2, 3, 6, 7, 8} print(a.symmetric_difference(b)) # 同上 # 子集/超集判断 print({1, 2}.issubset({1, 2, 3})) # True print({1, 2, 3}.issuperset({1, 2})) # True # ===== 实际应用:去重 ===== names = ["Alice", "Bob", "Alice", "Charlie", "Bob"] unique = list(set(names)) # ["Alice", "Bob", "Charlie"] # ===== 实际应用:找出共同标签 ===== user1_tags = {"python", "fastapi", "docker"} user2_tags = {"python", "react", "docker"} common = user1_tags & user2_tags # {"python", "docker"} # ===== frozenset:不可变集合(可以作为字典的键)===== fs = frozenset([1, 2, 3]) # fs.add(4) # ❌ 报错,frozenset 不能修改 cache = {fs: "cached_result"} # 可以作为字典的键

📋 速查:Set 运算符

运算运算符方法说明
并集a | ba.union(b)所有元素
交集a & ba.intersection(b)共有元素
差集a - ba.difference(b)a 有 b 没有
对称差a ^ ba.symmetric_difference(b)只在一方的元素
子集a <= ba.issubset(b)a 是否是 b 的子集
超集a >= ba.issuperset(b)a 是否是 b 的超集
添加s.add(x)添加元素
安全删除s.discard(x)不存在不报错
✏️ 填空
# 求两个集合的交集 a = {1, 2, 3} b = {2, 3, 4} common = a b # {2, 3} # 用 set 给列表去重 unique = list((names))

📌 Tuple 基础

# ===== 创建元组 ===== point = (3, 4) single = (42,) # 单元素元组必须加逗号!(42) 只是括号 empty = () from_list = tuple([1, 2, 3]) # ===== 访问(和 list 一样)===== print(point[0]) # 3 print(point[-1]) # 4 print(point[0:1]) # (3,) # ===== 不可变!不能修改 ===== # point[0] = 10 # ❌ TypeError: 不支持赋值 # ===== 常见用法 ===== # 1. 函数多返回值 def get_position() -> tuple[float, float]: return (37.7749, -122.4194) # 括号可省略 lat, lng = get_position() # 2. 字典的键(list 不能做字典键,tuple 可以) grid = {} grid[(0, 0)] = "origin" grid[(1, 0)] = "right" # 3. 解包 name, age, role = ("Alice", 28, "admin") # ===== tuple vs list 选择指南 ===== # 用 tuple:数据固定不变(坐标、RGB颜色、数据库行) # 用 list:数据会增删改(购物车、待办事项列表)

📛 namedtuple:给位置起名字

from collections import namedtuple # namedtuple 创建一个轻量级的不可变数据类 # 类似 TS 的 interface,但实例不可变 Point = namedtuple("Point", ["x", "y"]) User = namedtuple("User", "name age role") # 也可以用空格分隔 # 创建实例 p = Point(3, 4) print(p.x) # 3(用名字访问——比 p[0] 清晰多了!) print(p.y) # 4 print(p[0]) # 3(索引访问也行) # 不可变 # p.x = 10 # ❌ AttributeError # 转换 print(p._asdict()) # {"x": 3, "y": 4} p2 = p._replace(x=10) # 创建新实例 Point(x=10, y=4) # ===== 实际应用 ===== Color = namedtuple("Color", "r g b a") red = Color(255, 0, 0, 1.0) print(f"rgba({red.r}, {red.g}, {red.b}, {red.a})") # 现代 Python 更推荐用 dataclass(后面会学)

📋 速查:Tuple 与 namedtuple

特性tuplenamedtuplelist
可变
索引访问
名字访问p.x
做字典键
性能最快稍慢
内存占用最小较大
🧠 小测验

以下哪个是创建单元素元组的正确写法?

🔄 JS class vs Python class

概念JavaScript / TypeScriptPython
构造函数constructor(name) { this.name = name }def __init__(self, name): self.name = name
实例引用this(隐式传入)self(显式第一参数)
属性声明TS: name: string__init__ 中赋值
私有#private(ES2022)__name(名称修饰)
静态方法static method()@staticmethod
类方法@classmethod

🏗️ 定义类和创建实例

class User: """用户类""" # 类属性(所有实例共享,类似 TS 的 static 属性) default_role: str = "member" def __init__(self, name: str, age: int, email: str): """构造函数(类比 JS 的 constructor)""" # 实例属性(每个实例独立) self.name = name # self 就是 JS 的 this self.age = age self.email = email self._created_at = "2024-01-01" # _前缀 = protected(约定) self.__password = "secret" # __前缀 = private(名称修饰) def greet(self) -> str: """实例方法""" return f"Hi, I'm {self.name}, {self.age} years old" @classmethod def from_dict(cls, data: dict) -> "User": """类方法:工厂方法,从字典创建实例""" return cls(data["name"], data["age"], data["email"]) @staticmethod def validate_email(email: str) -> bool: """静态方法:不需要访问实例或类""" return "@" in email and "." in email def __repr__(self) -> str: """调试用的字符串表示""" return f"User(name={self.name!r}, age={self.age})" # 使用 alice = User("Alice", 28, "alice@example.com") print(alice.greet()) # Hi, I'm Alice, 28 years old print(alice.default_role) # "member"(通过实例访问类属性) # 类方法创建实例 bob = User.from_dict({"name": "Bob", "age": 30, "email": "bob@example.com"}) # 静态方法 print(User.validate_email("test@example.com")) # True

🔒 属性访问控制

# Python 用命名约定(不是语法强制)表示访问级别 class Account: def __init__(self, owner: str, balance: float): self.owner = owner # 公开属性 self._balance = balance # "保护"属性(约定不直接访问) self.__pin = "1234" # "私有"属性(名称修饰) @property def balance(self) -> float: """用 @property 创建只读属性(类比 TS 的 getter)""" return self._balance @balance.setter def balance(self, value: float): """setter 控制赋值逻辑""" if value < 0: raise ValueError("余额不能为负数") self._balance = value account = Account("Alice", 1000) print(account.balance) # 1000(通过 property 读取) account.balance = 2000 # 通过 setter 设置 # account.balance = -100 # ❌ ValueError
⚠️ 常见误区:① Python 没有真正的 private,__name 只是名称修饰(可以用 _ClassName__name 访问)。② self 必须是方法的第一个参数,但调用时不用传。③ Python 类不需要声明属性类型,在 __init__ 中赋值即可。

📋 速查:类定义语法

语法说明JS/TS 对应
class Foo:定义类class Foo {}
def __init__(self):构造函数constructor()
self.name = name实例属性this.name = name
def method(self):实例方法method() {}
@classmethod类方法(cls 参数)无直接对应
@staticmethod静态方法static method()
@property属性访问器get prop()
_name约定 protectedprotected
__name名称修饰 private#private
✏️ 填空
class Product: def (self, name: str, price: float): .name = name self.price = price @ def is_valid_price(price: float) -> bool: return price > 0

📌 单继承与 super()

# ===== 基本继承 ===== class Animal: def __init__(self, name: str, sound: str): self.name = name self.sound = sound def speak(self) -> str: return f"{self.name} says {self.sound}" class Dog(Animal): # Dog 继承 Animal def __init__(self, name: str, breed: str): super().__init__(name, "Woof") # 调用父类构造函数 self.breed = breed def fetch(self, item: str) -> str: return f"{self.name} fetches {item}" dog = Dog("Rex", "Labrador") print(dog.speak()) # Rex says Woof(继承的方法) print(dog.fetch("ball")) # Rex fetches ball # ===== 方法重写(Override)===== class Cat(Animal): def __init__(self, name: str): super().__init__(name, "Meow") def speak(self) -> str: # 重写父类方法 return f"{self.name} purrs softly..." cat = Cat("Whiskers") print(cat.speak()) # Whiskers purrs softly... # ===== isinstance / issubclass ===== print(isinstance(dog, Dog)) # True print(isinstance(dog, Animal)) # True print(issubclass(Dog, Animal)) # True

🔀 多态与抽象类

from abc import ABC, abstractmethod # ===== 抽象基类(类比 TS 的 interface/abstract class)===== class Shape(ABC): """形状抽象类——不能直接实例化""" @abstractmethod def area(self) -> float: """子类必须实现""" pass @abstractmethod def perimeter(self) -> float: pass class Circle(Shape): def __init__(self, radius: float): self.radius = radius def area(self) -> float: return 3.14159 * self.radius ** 2 def perimeter(self) -> float: return 2 * 3.14159 * self.radius class Rectangle(Shape): def __init__(self, width: float, height: float): self.width = width self.height = height def area(self) -> float: return self.width * self.height def perimeter(self) -> float: return 2 * (self.width + self.height) # 多态:不同类型统一接口 shapes: list[Shape] = [Circle(5), Rectangle(3, 4)] for shape in shapes: print(f"面积: {shape.area():.2f}, 周长: {shape.perimeter():.2f}")
⚠️ 常见误区:① Python 支持多继承,但要小心"菱形继承"问题。② super() 遵循 MRO(方法解析顺序),不一定调用直接父类。③ 抽象类不能直接实例化,必须实现所有抽象方法。

📋 速查:继承语法

语法说明
class Child(Parent):单继承
class C(A, B):多继承
super().__init__()调用父类构造函数
isinstance(obj, Class)实例判断
issubclass(A, B)子类判断
class Foo(ABC):抽象类
@abstractmethod抽象方法
ClassName.mro()查看方法解析顺序

📚 常用魔法方法

class Money: """自定义货币类,展示各种魔法方法""" def __init__(self, amount: float, currency: str = "CNY"): self.amount = amount self.currency = currency # ===== 字符串表示 ===== def __str__(self) -> str: """给用户看的(print 时调用)""" return f"{self.currency} {self.amount:.2f}" def __repr__(self) -> str: """给开发者看的(调试时调用)""" return f"Money({self.amount}, {self.currency!r})" # ===== 比较运算 ===== def __eq__(self, other) -> bool: """== 运算符""" return self.amount == other.amount and self.currency == other.currency def __lt__(self, other) -> bool: """< 运算符""" return self.amount < other.amount def __le__(self, other) -> bool: return self.amount <= other.amount # ===== 算术运算 ===== def __add__(self, other: "Money") -> "Money": """+ 运算符""" if self.currency != other.currency: raise ValueError("不同币种不能直接相加") return Money(self.amount + other.amount, self.currency) # ===== 容器相关 ===== def __len__(self) -> int: """len() 函数""" return int(self.amount) def __bool__(self) -> bool: """布尔判断""" return self.amount > 0 # 使用 a = Money(100, "CNY") b = Money(50, "CNY") print(a) # CNY 100.00(调用 __str__) print(repr(a)) # Money(100, 'CNY')(调用 __repr__) print(a + b) # CNY 150.00(调用 __add__) print(a > b) # True(调用 __lt__ 反向) print(a == Money(100, "CNY")) # True(调用 __eq__)

🔑 上下文管理器(with 语句)

class Timer: """用 with 语句计时的上下文管理器""" import time def __enter__(self): """进入 with 块时调用""" self.start = __import__("time").time() return self def __exit__(self, exc_type, exc_val, exc_tb): """离开 with 块时调用(即使发生异常)""" self.elapsed = __import__("time").time() - self.start print(f"⏱️ 耗时: {self.elapsed:.4f}s") return False # 不吞掉异常 # 使用(类似 JS 的 try-finally,但更优雅) with Timer(): total = sum(range(1000000))

📋 速查:常用魔法方法一览

方法触发场景说明
__init__创建实例构造函数
__str__print(obj)用户友好表示
__repr__调试器/REPL开发者表示
__eq__==相等比较
__lt__/__gt__< / >大小比较
__add__+加法运算
__len__len(obj)长度
__getitem__obj[key]索引访问
__setitem__obj[key] = val索引赋值
__contains__x in obj包含判断
__iter__for x in obj迭代
__call__obj()实例当函数调用
__enter__/__exit__with obj:上下文管理
__bool__bool(obj)布尔转换

📌 @dataclass 基础

from dataclasses import dataclass, field # dataclass 自动生成 __init__, __repr__, __eq__ 等方法 @dataclass class User: name: str age: int email: str role: str = "member" # 默认值 # 自动生成 __init__,不需要手写! alice = User(name="Alice", age=28, email="alice@example.com") print(alice) # User(name='Alice', age=28, email='alice@example.com', role='member') print(alice.name) # Alice # 自动生成 __eq__ bob = User(name="Alice", age=28, email="alice@example.com") print(alice == bob) # True # ===== frozen=True(不可变,类似 TS 的 Readonly)===== @dataclass(frozen=True) class Point: x: float y: float p = Point(3.0, 4.0) # p.x = 10 # ❌ FrozenInstanceError # ===== field() 自定义 ===== @dataclass class Config: host: str = "localhost" port: int = 8080 tags: list[str] = field(default_factory=list) # 可变默认值必须用 field! _internal: str = field(default="", repr=False) # 不显示在 repr 中

🔥 Pydantic BaseModel

from pydantic import BaseModel, Field, field_validator from typing import Optional # Pydantic = dataclass + 运行时类型校验 + 序列化 # 类比 TypeScript 的 Zod + interface class CreateUserRequest(BaseModel): """创建用户的请求模型""" name: str = Field(..., min_length=2, max_length=50, description="用户名") age: int = Field(..., ge=0, le=150, description="年龄") email: str role: str = "member" tags: list[str] = [] @field_validator("email") @classmethod def validate_email(cls, v: str) -> str: if "@" not in v: raise ValueError("邮箱格式不正确") return v.lower() # 创建时自动校验类型 user = CreateUserRequest( name="Alice", age=28, email="ALICE@Example.com" ) print(user.email) # alice@example.com(被 validator 转小写了) # 类型转换(Pydantic 会尝试转换) user2 = CreateUserRequest(name="Bob", age="30", email="bob@test.com") print(type(user2.age)) # <class 'int'>(字符串 "30" 自动转 int) # 序列化 print(user.model_dump()) # {"name": "Alice", "age": 28, ...} json_str = user.model_dump_json() # JSON 字符串 # 从字典/JSON 创建 data = {"name": "Charlie", "age": 25, "email": "c@test.com"} user3 = CreateUserRequest.model_validate(data)

⚖️ dataclass vs Pydantic 选择指南

用 @dataclass

  • 内部数据结构
  • 不需要运行时校验
  • 标准库,无额外依赖
  • 性能要求极高的场景

用 Pydantic BaseModel

  • API 请求/响应模型
  • 需要运行时类型校验
  • 需要 JSON 序列化
  • 外部数据(用户输入)

📋 速查:dataclass vs Pydantic

特性@dataclassPydantic BaseModel
运行时校验
类型转换
JSON 序列化需手动.model_dump_json()
不可变frozen=Truemodel_config = {"frozen": True}
字段校验需手动@field_validator
额外依赖无(标准库)需安装 pydantic
典型用途内部数据结构API 模型、配置

🔄 JS vs Python 异常处理

概念JavaScriptPython
捕获try { } catch (e) { }try: ... except Exception as e:
抛出throw new Error("msg")raise ValueError("msg")
finallyfinally { }finally:
成功分支else:(没有异常时执行)
异常类型只有 Error 一个基类丰富的内置异常层次

📌 完整语法

# ===== try/except/else/finally 完整结构 ===== def safe_divide(a: float, b: float) -> float | None: try: result = a / b except ZeroDivisionError: print("❌ 除数不能为零") return None except TypeError as e: print(f"❌ 类型错误: {e}") return None else: # 只有没有异常时才执行(JS 没有这个!) print(f"✅ 计算成功: {result}") return result finally: # 无论如何都会执行(和 JS 的 finally 一样) print("🔚 计算结束") safe_divide(10, 3) # ✅ 计算成功: 3.333... 🔚 计算结束 safe_divide(10, 0) # ❌ 除数不能为零 🔚 计算结束 # ===== 多异常捕获 ===== def process_data(data): try: value = data["key"] result = int(value) items = [1, 2, 3] return items[result] except KeyError: print("字典中没有这个键") except (ValueError, TypeError): # 捕获多种异常 print("类型转换失败") except IndexError: print("索引越界") except Exception as e: # 兜底:捕获所有其他异常 print(f"未预期的错误: {e}")

🏗️ 自定义异常

# ===== 自定义异常类 ===== class AppError(Exception): """应用基础异常""" def __init__(self, message: str, code: int = 500): super().__init__(message) self.code = code class NotFoundError(AppError): """资源未找到""" def __init__(self, resource: str, resource_id: int): super().__init__(f"{resource} #{resource_id} 不存在", code=404) self.resource = resource self.resource_id = resource_id class ValidationError(AppError): """数据校验失败""" def __init__(self, field: str, message: str): super().__init__(f"字段 {field} 校验失败: {message}", code=422) self.field = field # 使用 def get_user(user_id: int) -> dict: users = {1: {"name": "Alice"}} if user_id not in users: raise NotFoundError("User", user_id) return users[user_id] try: user = get_user(999) except NotFoundError as e: print(f"HTTP {e.code}: {e}") # HTTP 404: User #999 不存在 # ===== raise from(异常链)===== def load_config(path: str) -> dict: try: with open(path) as f: return {} # 解析配置 except FileNotFoundError as e: raise AppError(f"配置文件 {path} 不存在") from e # from e 保留原始异常信息,方便调试

📋 速查:常见内置异常

异常触发场景示例
ValueError值不合法int("abc")
TypeError类型不匹配"1" + 2
KeyError字典键不存在d["missing"]
IndexError索引越界[][0]
AttributeError属性不存在"".foo
FileNotFoundError文件不存在open("missing.txt")
ZeroDivisionError除以零1 / 0
PermissionError权限不足无写权限时写文件
ImportError导入失败import nonexistent
StopIteration迭代结束next(empty_iter)
✏️ 填空
# 捕获异常并获取异常对象 try: result = int("abc") ValueError e: print(f"错误: {e}") # 抛出自定义异常 NotFoundError("User", 123)

📌 with open() 上下文管理

# ===== with open() 是 Python 文件操作的标准写法 ===== # 自动关闭文件,即使发生异常(类比 JS 的 try-finally) # 读取文件 with open("data.txt", "r", encoding="utf-8") as f: content = f.read() # 读取全部内容 # 或者逐行读取 # lines = f.readlines() # 返回行列表 # line = f.readline() # 读取一行 # 写入文件 with open("output.txt", "w", encoding="utf-8") as f: f.write("Hello, Python!\n") f.write("第二行\n") # 追加内容 with open("log.txt", "a", encoding="utf-8") as f: f.write("新的日志行\n") # 逐行高效读取(适合大文件) with open("large_file.txt", "r", encoding="utf-8") as f: for line in f: # 逐行迭代,不会一次性加载到内存 process(line.strip())

📁 pathlib(推荐的路径操作库)

from pathlib import Path # ===== pathlib 比 os.path 更现代、更优雅 ===== # 类比 JS 的 path.join 和 fs 的各种方法 # 创建路径对象 p = Path("data/users") config_path = Path.home() / ".config" / "myapp" / "settings.json" # 路径操作 print(p.name) # "users" print(p.parent) # "data" print(p.suffix) # "" print(Path("file.tar.gz").suffixes) # [".tar", ".gz"] print(Path("file.py").stem) # "file" # 路径判断 print(p.exists()) # 路径是否存在 print(p.is_file()) # 是否是文件 print(p.is_dir()) # 是否是目录 # 创建目录 p.mkdir(parents=True, exist_ok=True) # 递归创建,已存在不报错 # 读写文件(最简洁的写法!) file_path = Path("hello.txt") file_path.write_text("你好世界", encoding="utf-8") content = file_path.read_text(encoding="utf-8") # 遍历目录 for f in Path(".").glob("*.py"): print(f.name) # 递归遍历 for f in Path(".").rglob("*.py"): print(f)

📄 JSON 读写

import json from pathlib import Path # ===== JSON 操作(后端最常用!)===== data = { "users": [ {"name": "Alice", "age": 28}, {"name": "Bob", "age": 30}, ], "total": 2 } # 写入 JSON 文件 with open("users.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) # ensure_ascii=False 保证中文不被转义 # indent=2 美化输出 # 读取 JSON 文件 with open("users.json", "r", encoding="utf-8") as f: loaded = json.load(f) # dict <-> JSON 字符串 json_str = json.dumps(data, ensure_ascii=False) # dict → str parsed = json.loads(json_str) # str → dict
⚠️ 常见误区:① 永远指定 encoding="utf-8",否则 Windows 默认用 GBK 会乱码。② json.dump(写入文件)和 json.dumps(转字符串)是两个不同的方法。③ 打开文件务必用 with,确保文件一定会被关闭。

📋 速查:文件操作

操作方法说明
读取全部f.read()返回字符串
逐行读取f.readlines()返回列表
写入f.write(text)写入字符串
打开模式"r"/"w"/"a"/"rb"/"wb"读/写/追加/二进制
路径拼接Path("a") / "b"pathlib 语法
创建目录Path.mkdir(parents=True)递归创建
JSON 写文件json.dump(data, f)dict → file
JSON 读文件json.load(f)file → dict
JSON 转字符串json.dumps(data)dict → str
JSON 解析json.loads(text)str → dict

🔄 TS vs Python 类型提示对照

概念TypeScriptPython(3.10+推荐)Python(旧写法)
可选string | nullstr | NoneOptional[str]
联合string | numberstr | intUnion[str, int]
列表string[]list[str]List[str]
字典Record<string, number>dict[str, int]Dict[str, int]
元组[string, number]tuple[str, int]Tuple[str, int]
字面量"admin" | "user"Literal["admin", "user"]同左
泛型T extends BaseTypeVar("T", bound=Base)同左
任意anyAny同左

📚 完整类型系统示例

from typing import TypeVar, Generic, Literal, Final, TypeAlias # ===== 基础类型提示(Python 3.10+ 推荐写法)===== name: str = "Alice" scores: list[int] = [95, 87, 92] config: dict[str, str | int] = {"host": "localhost", "port": 8080} point: tuple[float, float] = (3.14, 2.71) callback: callable = lambda x: x * 2 # type: ignore # ===== Literal 字面量类型(类似 TS 的字面量联合)===== Direction = Literal["up", "down", "left", "right"] def move(direction: Direction) -> str: return f"Moving {direction}" # ===== Final 常量 ===== MAX_RETRIES: Final = 3 # MAX_RETRIES = 5 # mypy 会警告 # ===== TypeAlias 类型别名 ===== UserId: TypeAlias = int UserDict: TypeAlias = dict[str, str | int | None] def get_user(user_id: UserId) -> UserDict: return {"id": user_id, "name": "Alice"} # ===== TypeVar 泛型(类似 TS 的 <T>)===== T = TypeVar("T") def first(items: list[T]) -> T | None: """返回列表第一个元素,类似 TS 的 function first<T>(items: T[]): T""" return items[0] if items else None result = first([1, 2, 3]) # 推断 result 是 int | None result2 = first(["a", "b"]) # 推断 result2 是 str | None # ===== Generic 泛型类 ===== class Stack(Generic[T]): def __init__(self) -> None: self._items: list[T] = [] def push(self, item: T) -> None: self._items.append(item) def pop(self) -> T: return self._items.pop() int_stack: Stack[int] = Stack() int_stack.push(42) # int_stack.push("hello") # mypy 会警告类型不匹配
💡 实际建议:Python 3.10+ 优先使用 X | Y 语法替代 Optional[X]Union[X, Y]。用 list[str] 替代 List[str]。保持和 TypeScript 类似的简洁风格。

📋 速查:类型提示语法

类型语法TS 对应
可选str | Nonestring | null
联合int | strnumber | string
列表list[str]string[]
字典dict[str, int]Record<string, number>
元组tuple[str, int][string, number]
集合set[str]Set<string>
字面量Literal["a","b"]"a" | "b"
泛型TypeVar("T")<T>
泛型类class Foo(Generic[T]):class Foo<T>
常量Finalconst
类型别名TypeAliastype X = ...
任意Anyany

🔄 Jest vs pytest

概念Jest / Vitestpytest
测试文件*.test.tstest_*.py
测试函数test("desc", () => {})def test_desc():
断言expect(x).toBe(y)assert x == y
前置处理beforeEach@pytest.fixture
Mockjest.fn()unittest.mock.Mock()
运行npm testpytest

📌 pytest 基础

# test_calculator.py import pytest # ===== 基础测试函数 ===== def add(a: int, b: int) -> int: return a + b def test_add_positive(): assert add(2, 3) == 5 def test_add_negative(): assert add(-1, -1) == -2 def test_add_zero(): assert add(0, 0) == 0 # ===== 测试异常 ===== def divide(a: float, b: float) -> float: if b == 0: raise ValueError("除数不能为零") return a / b def test_divide_by_zero(): with pytest.raises(ValueError, match="除数不能为零"): divide(10, 0) # ===== 参数化测试(一组数据跑多次)===== @pytest.mark.parametrize("a, b, expected", [ (1, 1, 2), (0, 0, 0), (-1, 1, 0), (100, 200, 300), ]) def test_add_parametrize(a, b, expected): assert add(a, b) == expected

🔧 Fixture(依赖注入)

import pytest # fixture = 测试的前置准备(类似 beforeEach) @pytest.fixture def sample_user() -> dict: """每个测试自动获得一个新的用户字典""" return {"name": "Alice", "age": 28, "email": "alice@test.com"} @pytest.fixture def user_list() -> list[dict]: return [ {"name": "Alice", "age": 28}, {"name": "Bob", "age": 30}, ] # 测试函数通过参数名自动注入 fixture def test_user_name(sample_user): assert sample_user["name"] == "Alice" def test_user_count(user_list): assert len(user_list) == 2 # ===== Mock ===== from unittest.mock import Mock, patch def fetch_user_from_api(user_id: int) -> dict: """实际会调用外部 API""" import requests resp = requests.get(f"https://api.example.com/users/{user_id}") return resp.json() def test_fetch_user(): """用 Mock 替代真实的 API 调用""" with patch("requests.get") as mock_get: mock_get.return_value.json.return_value = {"name": "Alice"} result = fetch_user_from_api(1) assert result["name"] == "Alice" mock_get.assert_called_once()
# 运行测试 # pytest # 运行所有测试 # pytest test_calculator.py # 运行指定文件 # pytest -v # 详细输出 # pytest -k "test_add" # 按名字过滤 # pytest --cov=app # 覆盖率报告

📋 速查:pytest 常用功能

功能语法说明
测试函数def test_xxx():以 test_ 开头
断言assert x == y直接用 assert
异常断言pytest.raises(Error)验证抛出异常
Fixture@pytest.fixture依赖注入
@pytest.mark.parametrize多组数据测试
Mockunittest.mock.patch替换依赖
运行全部pytest自动发现测试
详细输出pytest -v显示每个测试
覆盖率pytest --cov=app需安装 pytest-cov
按名过滤pytest -k "pattern"按测试名筛选

🔄 视角切换:前端 vs 后端

前端视角(发请求)

fetch("/api/users", { method: "POST", body: JSON.stringify(data) })

关心:请求参数、响应格式、错误处理

后端视角(处理请求)

@app.post("/api/users")
def create_user(data):

关心:校验输入、业务逻辑、数据持久化、权限检查

📊 HTTP 方法语义

方法语义幂等请求体典型用途
GET获取资源查询用户列表、获取详情
POST创建资源创建用户、提交表单
PUT全量更新更新整个用户信息
PATCH部分更新更新用户昵称
DELETE删除资源可选删除用户

📋 HTTP 状态码完整对照表

状态码含义前端处理建议后端返回场景
200OK 成功正常处理响应GET/PUT/PATCH 成功
201Created 已创建跳转或更新列表POST 创建成功
204No Content无需处理响应体DELETE 成功
301永久重定向自动跟随URL 永久变更
302临时重定向自动跟随临时跳转
400Bad Request提示输入错误参数校验失败
401Unauthorized跳转登录未登录/Token过期
403Forbidden提示无权限已登录但无权限
404Not Found展示404页面资源不存在
409Conflict提示冲突唯一约束冲突
422Unprocessable展示校验错误数据格式正确但语义错误
429Too Many Requests稍后重试触发限流
500Internal Error展示错误页面服务端异常(bug)
502Bad Gateway稍后重试上游服务异常
503Service Unavailable维护提示服务暂不可用

📍 请求参数位置

# ===== HTTP 请求中数据的三个位置 ===== # 1. Path Parameters(路径参数)—— 标识资源 # GET /api/users/42 → user_id = 42 # DELETE /api/posts/123 → post_id = 123 # 2. Query Parameters(查询参数)—— 过滤和分页 # GET /api/users?page=1&limit=20&role=admin # → page = 1, limit = 20, role = "admin" # 3. Request Body(请求体)—— 传递数据 # POST /api/users # Body: {"name": "Alice", "email": "alice@test.com"} # ===== RESTful 资源命名规范 ===== # 用复数名词,不用动词 # ✅ GET /api/users 获取用户列表 # ✅ GET /api/users/42 获取单个用户 # ✅ POST /api/users 创建用户 # ✅ PUT /api/users/42 更新用户 # ✅ DELETE /api/users/42 删除用户 # ❌ GET /api/getUsers 不要用动词 # ❌ POST /api/deleteUser 不要用 POST 代替 DELETE

📋 速查:HTTP 状态码分类

范围分类常见
1xx信息100 Continue
2xx成功200, 201, 204
3xx重定向301, 302, 304
4xx客户端错误400, 401, 403, 404, 422
5xx服务端错误500, 502, 503
🧠 小测验

前端调用 POST /api/users 创建用户成功,后端应该返回哪个状态码最合适?

🚀 快速开始

# main.py from fastapi import FastAPI from pydantic import BaseModel app = FastAPI(title="My API", version="1.0.0") # ===== 最简单的 GET 接口 ===== @app.get("/") def root(): return {"message": "Hello, FastAPI!"} # ===== 路径参数 ===== @app.get("/users/{user_id}") def get_user(user_id: int): """FastAPI 自动校验 user_id 是 int,不是就返回 422""" return {"user_id": user_id, "name": f"User {user_id}"} # ===== 查询参数 ===== @app.get("/items") def list_items(page: int = 1, limit: int = 20, q: str | None = None): """GET /items?page=1&limit=20&q=python""" return {"page": page, "limit": limit, "query": q} # ===== 请求体(用 Pydantic 模型)===== class CreateUserRequest(BaseModel): name: str email: str age: int | None = None @app.post("/users", status_code=201) def create_user(user: CreateUserRequest): """POST /users,请求体自动校验""" return {"id": 1, **user.model_dump()} # 启动命令 # uvicorn main:app --reload # 访问文档:http://localhost:8000/docs

🔄 Express vs FastAPI 对比

功能ExpressFastAPI
路由定义app.get("/path", handler)@app.get("/path")
路径参数req.params.iddef f(id: int):(自动注入)
查询参数req.query.pagedef f(page: int = 1):
请求体req.body(手动校验)Pydantic 自动校验
类型校验需要 Zod/Joi内置(Pydantic)
API 文档需要 Swagger 中间件自动生成 /docs
异步默认异步支持 async/await

📄 响应模型与状态码

from fastapi import FastAPI, HTTPException from pydantic import BaseModel app = FastAPI() class UserResponse(BaseModel): id: int name: str email: str # 响应模型:自动过滤返回字段,只返回 UserResponse 中定义的 @app.get("/users/{user_id}", response_model=UserResponse) def get_user(user_id: int): # 假设从数据库查到的数据有很多字段 user = {"id": user_id, "name": "Alice", "email": "a@test.com", "password": "secret"} # response_model 会自动过滤掉 password return user # 错误处理 @app.get("/users/{user_id}") def get_user_v2(user_id: int): if user_id > 100: raise HTTPException(status_code=404, detail="用户不存在") return {"id": user_id, "name": "Alice"}
💡 核心优势:FastAPI 的类型提示既是文档(自动生成 Swagger),又是校验(请求参数自动校验),还是 IDE 补全的基础。这是"一次声明,三重收益"。

📋 速查:FastAPI 基础语法

语法说明示例
@app.get("/path")GET 路由查询操作
@app.post("/path")POST 路由创建操作
@app.put("/path")PUT 路由全量更新
@app.delete("/path")DELETE 路由删除操作
def f(id: int):路径参数自动从 URL 提取
def f(q: str = None):查询参数自动从 query string 提取
def f(body: Model):请求体Pydantic 模型校验
response_model=Model响应模型过滤返回字段
status_code=201设置状态码创建成功返回 201
HTTPException抛出 HTTP 错误404, 403 等
🎯 任务:写一个待办事项 CRUD API
用 FastAPI 实现一个简单的待办事项 API(内存存储即可),支持:创建、查询列表、查询单个、更新状态、删除。
查看参考思路
from fastapi import FastAPI, HTTPException from pydantic import BaseModel app = FastAPI() todos = {} next_id = 1 class TodoCreate(BaseModel): title: str done: bool = False class TodoUpdate(BaseModel): title: str | None = None done: bool | None = None @app.post("/todos", status_code=201) def create_todo(todo: TodoCreate): global next_id todos[next_id] = {**todo.model_dump(), "id": next_id} next_id += 1 return todos[next_id - 1] @app.get("/todos") def list_todos(): return list(todos.values()) @app.get("/todos/{todo_id}") def get_todo(todo_id: int): if todo_id not in todos: raise HTTPException(404, "待办不存在") return todos[todo_id] @app.patch("/todos/{todo_id}") def update_todo(todo_id: int, data: TodoUpdate): if todo_id not in todos: raise HTTPException(404, "待办不存在") for k, v in data.model_dump(exclude_unset=True).items(): todos[todo_id][k] = v return todos[todo_id] @app.delete("/todos/{todo_id}", status_code=204) def delete_todo(todo_id: int): if todo_id not in todos: raise HTTPException(404, "待办不存在") del todos[todo_id]

🔧 依赖注入(Depends)

from fastapi import FastAPI, Depends, HTTPException, Header app = FastAPI() # ===== 依赖注入:类比 Express 的中间件,但更灵活 ===== # 简单依赖:共用查询参数 def common_params(page: int = 1, limit: int = 20): """共用的分页参数""" return {"page": page, "limit": limit, "skip": (page - 1) * limit} @app.get("/users") def list_users(params: dict = Depends(common_params)): return {"pagination": params, "users": []} @app.get("/posts") def list_posts(params: dict = Depends(common_params)): return {"pagination": params, "posts": []} # ===== 认证依赖 ===== def get_current_user(authorization: str = Header(None)): """从 Header 提取并验证 Token""" if not authorization or not authorization.startswith("Bearer "): raise HTTPException(status_code=401, detail="未登录") token = authorization.split(" ")[1] # 实际项目中这里会验证 JWT return {"user_id": 1, "name": "Alice"} @app.get("/profile") def get_profile(user: dict = Depends(get_current_user)): """需要登录才能访问""" return {"profile": user} # ===== 依赖链 ===== def require_admin(user: dict = Depends(get_current_user)): """需要管理员权限""" if user.get("role") != "admin": raise HTTPException(status_code=403, detail="需要管理员权限") return user @app.delete("/users/{user_id}") def delete_user(user_id: int, admin: dict = Depends(require_admin)): return {"deleted": user_id}

📁 路由分组(APIRouter)

# ===== 用 APIRouter 组织大型项目的路由 ===== # routers/users.py from fastapi import APIRouter router = APIRouter(prefix="/api/users", tags=["用户管理"]) @router.get("/") def list_users(): return [] @router.post("/") def create_user(): return {"created": True} # main.py from fastapi import FastAPI # from routers.users import router as user_router app = FastAPI() # app.include_router(user_router) # 现在 /api/users/ 和 /api/users/POST 都可以访问了

🔄 中间件

import time from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware app = FastAPI() # ===== CORS 中间件(前端同学最关心的!)===== app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000"], # 前端地址 allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ===== 自定义中间件:请求计时 ===== @app.middleware("http") async def add_process_time_header(request: Request, call_next): start = time.time() response = await call_next(request) elapsed = time.time() - start response.headers["X-Process-Time"] = f"{elapsed:.4f}" return response # ===== 全局异常处理 ===== from fastapi.responses import JSONResponse @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): return JSONResponse( status_code=500, content={"success": False, "error": str(exc)}, )

🏷️ 业务错误码设计

from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from fastapi.exceptions import RequestValidationError app = FastAPI() # ===== 自定义业务异常(分层设计)===== class AppException(Exception): """所有业务异常的基类""" def __init__(self, message: str, code: str, status: int = 400): self.message = message self.code = code # 业务错误码:模块前缀 + 编号 self.status = status class UserException(AppException): USER_EXISTS = "USER_001" # 用户已存在 NOT_FOUND = "USER_002" # 用户不存在 WRONG_PASS = "USER_003" # 密码错误 class OrderException(AppException): NOT_FOUND = "ORDER_001" INSUFFICIENT = "ORDER_002" # 余额不足 # ===== 全局异常处理器:统一错误响应格式 ===== @app.exception_handler(AppException) async def app_exception_handler(request: Request, exc: AppException): return JSONResponse( status_code=exc.status, content={"success": False, "code": exc.code, "error": exc.message}, ) @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): return JSONResponse( status_code=422, content={"success": False, "code": "INVALID_PARAMS", "error": str(exc.errors())}, ) # ===== 使用方式 ===== @app.post("/api/users") async def create_user(email: str): if await user_exists(email): raise AppException( # ← 抛出业务异常 message="该邮箱已注册", code=UserException.USER_EXISTS, # ← 业务错误码 status=409, ) # ... # 前端收到:{"success": false, "code": "USER_001", "error": "该邮箱已注册"}

📋 速查:FastAPI 进阶特性

功能语法用途
依赖注入Depends(func)共用逻辑、认证、数据库
路由分组APIRouter(prefix=...)大型项目路由组织
CORSCORSMiddleware跨域配置
中间件@app.middleware("http")请求/响应拦截
异常处理@app.exception_handler全局错误处理
后台任务BackgroundTasks异步执行不阻塞响应
请求头Header()提取请求头
CookieCookie()提取 Cookie

🔄 Zod vs Pydantic

功能Zod (TypeScript)Pydantic (Python)
定义模型z.object({ name: z.string() })class M(BaseModel): name: str
校验schema.parse(data)M.model_validate(data)
字段约束z.string().min(2).max(50)Field(min_length=2, max_length=50)
自定义校验.refine(fn)@field_validator
嵌套模型支持支持
序列化手动.model_dump() / .model_dump_json()

📚 完整 Pydantic 示例

from pydantic import BaseModel, Field, field_validator, model_validator from datetime import datetime from enum import Enum # ===== 枚举类型 ===== class UserRole(str, Enum): admin = "admin" member = "member" guest = "guest" # ===== 嵌套模型 ===== class Address(BaseModel): city: str street: str zip_code: str = Field(..., pattern=r"^\d{6}$") # 正则校验 class CreateUserRequest(BaseModel): """创建用户请求模型""" name: str = Field(..., min_length=2, max_length=50, description="用户名") email: str = Field(..., description="邮箱地址") age: int = Field(..., ge=0, le=150, description="年龄") role: UserRole = UserRole.member address: Address | None = None tags: list[str] = Field(default_factory=list, max_length=10) # 字段校验器 @field_validator("email") @classmethod def validate_email(cls, v: str) -> str: if "@" not in v or "." not in v: raise ValueError("邮箱格式不正确") return v.lower() @field_validator("name") @classmethod def validate_name(cls, v: str) -> str: if not v.strip(): raise ValueError("名字不能为空白") return v.strip() # 模型级别校验(跨字段校验) @model_validator(mode="after") def check_admin_age(self): if self.role == UserRole.admin and self.age < 18: raise ValueError("管理员必须年满 18 岁") return self # ===== 使用 ===== user = CreateUserRequest( name="Alice", email="ALICE@Example.com", age=28, address={"city": "北京", "street": "朝阳路", "zip_code": "100000"} ) print(user.email) # alice@example.com(被校验器转小写) print(user.model_dump()) # 转为字典 print(user.model_dump_json(indent=2)) # 转为 JSON 字符串 # 从字典创建(常用于 API 输入) data = {"name": "Bob", "email": "bob@test.com", "age": 30} user2 = CreateUserRequest.model_validate(data)

📋 速查:Pydantic 常用功能

功能语法说明
必填字段name: str无默认值 = 必填
可选字段name: str = "default"有默认值 = 可选
字段约束Field(ge=0, le=100)最小值/最大值
字符串约束Field(min_length=2)最短/最长
正则Field(pattern=r"...")正则校验
字段校验@field_validator("name")自定义字段校验
模型校验@model_validator跨字段校验
转字典.model_dump()序列化为 dict
转 JSON.model_dump_json()序列化为 JSON
从字典创建.model_validate(data)反序列化
嵌套模型addr: Address模型嵌套

📐 统一响应格式

from pydantic import BaseModel from typing import TypeVar, Generic T = TypeVar("T") # ===== 统一响应信封(前端最爱的 API 格式)===== class ApiResponse(BaseModel, Generic[T]): """统一 API 响应格式""" success: bool data: T | None = None error: str | None = None meta: dict | None = None # 成功响应 def ok(data, **meta) -> dict: resp = {"success": True, "data": data} if meta: resp["meta"] = meta return resp # 错误响应 def fail(error: str, code: int = 400) -> dict: return {"success": False, "error": error} # ===== 使用示例 ===== # 成功: # {"success": true, "data": {"id": 1, "name": "Alice"}} # 列表 + 分页: # {"success": true, "data": [...], "meta": {"total": 100, "page": 1, "limit": 20}} # 错误: # {"success": false, "error": "用户不存在"}

📄 分页设计

# ===== 标准分页参数 ===== from fastapi import Query def pagination_params( page: int = Query(1, ge=1, description="页码"), limit: int = Query(20, ge=1, le=100, description="每页条数"), ): return {"page": page, "limit": limit, "skip": (page - 1) * limit} # 请求:GET /api/users?page=2&limit=10 # 响应: # { # "success": true, # "data": [...], # "meta": { # "total": 156, # "page": 2, # "limit": 10, # "total_pages": 16 # } # }

🏷️ RESTful 命名规范

操作方法路径状态码
获取列表GET/api/users200
获取详情GET/api/users/:id200
创建POST/api/users201
全量更新PUT/api/users/:id200
部分更新PATCH/api/users/:id200
删除DELETE/api/users/:id204
嵌套资源GET/api/users/:id/posts200

📊 错误码设计

# ===== 推荐的业务错误码体系 ===== # HTTP 状态码 + 业务错误码 双层设计 class ErrorCode: # 通用错误 INVALID_PARAMS = "E1001" # 参数校验失败 UNAUTHORIZED = "E1002" # 未登录 FORBIDDEN = "E1003" # 无权限 NOT_FOUND = "E1004" # 资源不存在 # 用户模块 USER_EXISTS = "E2001" # 用户已存在 USER_NOT_FOUND = "E2002" # 用户不存在 INVALID_PASSWORD = "E2003" # 密码错误 # 订单模块 ORDER_NOT_FOUND = "E3001" # 订单不存在 INSUFFICIENT_BALANCE = "E3002" # 余额不足 # 错误响应格式 # { # "success": false, # "error": "用户名已存在", # "error_code": "E2001" # }

🗂️ API 版本控制

from fastapi import APIRouter, FastAPI app = FastAPI() # ===== URL 版本控制(最常用)===== v1 = APIRouter(prefix="/api/v1", tags=["v1"]) v2 = APIRouter(prefix="/api/v2", tags=["v2"]) @v1.get("/users") def list_users_v1(): return [{"id": 1, "name": "Alice"}] # 旧格式 @v2.get("/users") def list_users_v2(cursor: str | None = None): # v2 新增 cursor 分页 return {"data": [{"id": 1, "name": "Alice"}], "next_cursor": None} app.include_router(v1) app.include_router(v2) # ===== 向后兼容策略 ===== # ✅ 只加字段,不删字段(旧客户端不会崩) # ✅ 新字段给默认值(Optional 或有默认值) # ❌ 不改已有字段的类型 # ❌ 不删已有字段 # ❌ 不改 URL 路径(改了旧客户端全挂) # ===== 废弃接口(Deprecation)===== @v1.get("/users/{id}", deprecated=True) # ← 在 Swagger 文档中标记废弃 def get_user_v1(id: int): """⚠️ 此接口已废弃,请使用 /api/v2/users/{id}""" return {"id": id}

📋 速查:REST 设计规则

规则示例
用复数名词/users 不是 /user
不用动词POST /users 不是 /createUser
用小写和连字符/user-profiles
版本前缀/api/v1/users
统一响应格式{success, data, error}
分页参数?page=1&limit=20
排序参数?sort=created_at&order=desc
过滤参数?role=admin&status=active
JavaScriptPython asyncio
async function fetchUser() {}async def fetch_user(): ...
await fetch(url)await httpx.get(url)
Promise.all([p1, p2])asyncio.gather(coro1, coro2)
setTimeout(fn, 1000)await asyncio.sleep(1)
Node.js 事件循环自动运行asyncio.run(main()) 显式启动

⚙️ 事件循环

asyncio 有一个事件循环,不断检查:有没有已完成 I/O 的协程可以恢复?没有就继续等。CPU 密集型任务无法受益——因为计算时不会暂停让出控制权。

🚦 I/O bound vs CPU bound

async 适合 I/O bound:等网络请求、等数据库查询、等文件读写。
CPU bound(计算密集)用 ProcessPoolExecutor 或多进程,async 帮不了你。

🔀 协程 vs 线程 vs 进程

协程(coroutine):单线程,主动让出,无锁,轻量。
线程:被动切换,有 GIL 限制。
进程:真正并行,内存隔离,开销大。FastAPI 用协程处理请求。

🔧 基础 async/await

import asyncio # ===== 基础协程 ===== async def fetch_user(user_id: int) -> dict: """模拟异步数据库查询""" await asyncio.sleep(0.1) # ← 模拟 I/O 等待(不阻塞事件循环) return {"id": user_id, "name": f"User{user_id}"} async def main(): user = await fetch_user(1) # ← await 等待协程完成 print(user) # 启动事件循环(脚本入口) asyncio.run(main()) # ===== 在 FastAPI 中,事件循环已由框架管理,直接写 async def 即可 =====

⚡ asyncio.gather:并发执行多个任务

import asyncio import time async def fetch_user(uid: int): await asyncio.sleep(0.2) # 模拟 200ms 数据库查询 return {"id": uid} async def fetch_posts(uid: int): await asyncio.sleep(0.3) # 模拟 300ms 数据库查询 return [{"post": f"Post by {uid}"}] # ===== 顺序执行(慢!总计 500ms)===== async def sequential(): user = await fetch_user(1) posts = await fetch_posts(1) # 等 user 完成才开始 return user, posts # ===== 并发执行(快!总计 300ms)===== async def concurrent(): user, posts = await asyncio.gather( fetch_user(1), fetch_posts(1), # ← 两个任务同时进行 ) return user, posts # 💡 类比 JS: Promise.all([fetchUser(), fetchPosts()]) # 规则:互不依赖的 I/O 操作,用 gather 并发

🗄️ 连接池:为什么不能每次新建连接

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker # ===== 错误做法:每次请求新建连接(慢且耗资源)===== # async def get_user(uid): # conn = await create_connection() # 每次握手 100ms+ # ... # ===== 正确做法:连接池(启动时创建,复用连接)===== engine = create_async_engine( "postgresql+asyncpg://user:pass@localhost/db", pool_size=10, # 维持 10 个连接常驻 max_overflow=5, # 突发时最多再借 5 个 pool_pre_ping=True, # 自动检测并恢复断开的连接 ) AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession) # FastAPI 依赖注入数据库会话(连接池自动管理) async def get_db(): async with AsyncSessionLocal() as session: yield session # ← 请求结束自动归还连接到池中

🔄 FastAPI 中 async def vs def

from fastapi import FastAPI import asyncio, time app = FastAPI() # ===== async def:推荐用于所有 I/O 操作 ===== @app.get("/async") async def async_endpoint(): await asyncio.sleep(1) # ← 不阻塞事件循环,其他请求可同时处理 return {"type": "async"} # ===== def(普通函数):FastAPI 会在线程池中运行 ===== @app.get("/sync") def sync_endpoint(): time.sleep(1) # ← FastAPI 自动用线程池,不会阻塞事件循环 return {"type": "sync"} # ===== 实践建议 ===== # - 数据库查询、HTTP 请求 → async def + await # - CPU 密集计算 → def(FastAPI 会用线程池) # - 纯逻辑计算(无 I/O)→ def 更简单,性能差不多

📋 速查:asyncio 常用 API

API用途类比 JS
asyncio.run(coro)启动事件循环顶层 await
await asyncio.sleep(n)异步等待 n 秒await sleep(n)
asyncio.gather(*coros)并发运行多个协程Promise.all()
asyncio.create_task(coro)后台运行(不等待)Promise(不 await)
asyncio.wait_for(coro, timeout)带超时的 awaitPromise.race()
async for item in aiter异步迭代for await...of
async with ctx异步上下文管理await using
✏️ 填空题:并发执行两个查询
user, orders = await asyncio.( fetch_user(uid), fetch_orders(uid), )
🧠 小测验

你有一个接口需要同时查询数据库和调用第三方 API,应该怎么写?

🤔 为什么用 httpx

老牌的 requests 库不支持 async,在 asyncio 项目里会阻塞事件循环。httpx 有完全相同的 API,还支持 async withawait,是 FastAPI 项目的首选 HTTP 客户端。

⏱️ 超时和重试的重要性

调用外部服务时:网络可能抖动,接口可能超时。不设超时 = 一个慢接口拖垮所有请求。建议:连接超时 5s,读取超时 30s。重要接口加自动重试(指数退避)。

🔧 httpx 基础用法

import httpx # ===== 同步用法(简单脚本场景)===== with httpx.Client(timeout=10.0) as client: # GET 请求 resp = client.get("https://api.example.com/users/1") resp.raise_for_status() # 4xx/5xx 自动抛出异常 user = resp.json() # POST 请求 resp = client.post( "https://api.example.com/users", json={"name": "Alice", "email": "alice@example.com"}, headers={"Authorization": "Bearer sk-xxx"}, ) new_user = resp.json() # ===== 细粒度超时设置 ===== timeout = httpx.Timeout( connect=5.0, # 建立连接超时 read=30.0, # 读取响应超时 write=10.0, # 发送请求超时 pool=5.0, # 从连接池获取连接超时 ) client = httpx.Client(timeout=timeout)

⚡ 异步用法(FastAPI 项目推荐)

import httpx from fastapi import FastAPI app = FastAPI() # ===== 方式一:每次请求创建(简单但有开销)===== @app.get("/github/{username}") async def get_github_user(username: str): async with httpx.AsyncClient(timeout=10.0) as client: resp = await client.get(f"https://api.github.com/users/{username}") resp.raise_for_status() return resp.json() # ===== 方式二:复用 AsyncClient(推荐,性能更好)===== # 在应用启动时创建,关闭时释放 _http_client: httpx.AsyncClient | None = None @app.on_event("startup") async def startup(): global _http_client _http_client = httpx.AsyncClient(timeout=httpx.Timeout(connect=5, read=30)) @app.on_event("shutdown") async def shutdown(): await _http_client.aclose() @app.get("/weather/{city}") async def get_weather(city: str): resp = await _http_client.get( "https://api.weatherapi.com/v1/current.json", params={"key": "API_KEY", "q": city}, ) resp.raise_for_status() return resp.json()

🔁 重试策略(tenacity)

from tenacity import retry, stop_after_attempt, wait_exponential import httpx # ===== 自动重试:最多 3 次,指数退避(1s → 2s → 4s)===== @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10), reraise=True, # 全部失败后抛出原始异常 ) async def call_payment_api(order_id: str) -> dict: async with httpx.AsyncClient(timeout=10) as client: resp = await client.post( "https://payment.example.com/pay", json={"order_id": order_id}, ) resp.raise_for_status() return resp.json() # ===== 使用场景 ===== # 重试:网络抖动、临时 503 # 不重试:400/401/403/404(业务错误,重试没用) # 💡 规则:状态码 5xx 或网络异常才值得重试

🛡️ 防御性编程:永远不信任外部返回值

from pydantic import BaseModel import httpx class GithubUser(BaseModel): login: str id: int name: str | None = None # ← 字段可能不存在,设默认值 email: str | None = None async def get_github_user(username: str) -> GithubUser | None: try: async with httpx.AsyncClient(timeout=10) as client: resp = await client.get(f"https://api.github.com/users/{username}") if resp.status_code == 404: return None # 用户不存在 resp.raise_for_status() # 其他 4xx/5xx 抛异常 data = resp.json() return GithubUser.model_validate(data) # ← Pydantic 校验并过滤字段 except httpx.TimeoutException: raise RuntimeError("GitHub API 超时,请稍后重试") except httpx.HTTPStatusError as e: raise RuntimeError(f"GitHub API 错误: {e.response.status_code}") # ===== 防御规则 ===== # 1. 用 Pydantic 校验外部返回值,不直接 dict["field"] # 2. 所有字段给默认值(外部可能随时增删字段) # 3. 分类处理:业务错误(404) vs 系统错误(5xx) vs 网络错误

📋 速查:HTTP 状态码处理策略

状态码含义处理策略
200/201成功正常解析响应体
400请求参数错误检查入参,不要重试
401未认证刷新 token 后重试一次
403无权限不要重试,记录日志
404资源不存在返回 None 或业务异常
429限流读取 Retry-After 头,等待后重试
500/502/503服务端错误指数退避重试
超时网络/响应慢指数退避重试
✏️ 填空题:正确设置连接超时和读取超时
timeout = httpx.(connect=, read=30)
🧠 小测验

调用第三方支付接口,收到 429 Too Many Requests,应该怎么处理?

模式存储位置适用场景优缺点
Session服务端内存/Redis传统 Web、单体应用有状态,水平扩展需共享 Session
JWT客户端(Header/Cookie)SPA、移动端、微服务通常做成无状态;如需吊销,常配合黑名单或 token 版本号
API Key客户端 Header服务间调用、开发者 API简单;需安全传输,无过期机制

🔑 JWT 工作原理

JWT = Header.Payload.Signature,三段 Base64URL 编码文本用 . 连接。
Header:算法(HS256)
Payload:用户 ID、角色、过期时间
Signature:用密钥签名,防篡改
只做签名校验时,服务端只需要密钥即可验证;但若要做吊销、封禁、权限变更即时生效,通常仍要结合数据库或缓存。

🛡️ XSS 与 CSRF 防护

XSS(跨站脚本):攻击者注入恶意脚本。防护:
① 不拼接 HTML,用模板引擎或安全渲染方式
② 设置 Content-Security-Policy 响应头

CSRF(跨站请求伪造):防护:
① 若鉴权信息放在 Authorization Header,通常不会触发传统 CSRF;若放在 Cookie,仍需额外防护
② 使用 Cookie 时,结合 SameSite、CSRF Token 等机制

🔧 JWT 生成与验证(python-jose)

from datetime import datetime, timedelta, timezone from jose import JWTError, jwt from pydantic import BaseModel # ===== 配置 ===== SECRET_KEY = "your-secret-key-keep-it-safe" # 生产环境用环境变量 ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 # ===== 生成 Token ===== def create_access_token(user_id: int, role: str = "user") -> str: payload = { "sub": str(user_id), # subject(主体,通常是用户 ID) "role": role, "exp": datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES), "iat": datetime.now(timezone.utc), # issued at } return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM) # ===== 验证 Token ===== def verify_token(token: str) -> dict: try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) user_id = payload.get("sub") if user_id is None: raise ValueError("token 无效") return {"user_id": int(user_id), "role": payload.get("role")} except JWTError: raise ValueError("token 无效或已过期")

🔒 FastAPI 完整认证依赖

from fastapi import FastAPI, Depends, HTTPException, Header from pydantic import BaseModel app = FastAPI() # ===== 认证依赖(替代 fastapi-advanced 里的注释版本)===== async def get_current_user(authorization: str = Header(None)) -> dict: if not authorization or not authorization.startswith("Bearer "): raise HTTPException(status_code=401, detail="未提供认证 Token") token = authorization.split(" ")[1] try: return verify_token(token) # ← 使用上面的 verify_token except ValueError as e: raise HTTPException(status_code=401, detail=str(e)) # ===== 需要登录的接口 ===== @app.get("/api/profile") async def get_profile(user: dict = Depends(get_current_user)): return {"user_id": user["user_id"], "role": user["role"]} # ===== 需要管理员的接口 ===== async def require_admin(user: dict = Depends(get_current_user)): if user["role"] != "admin": raise HTTPException(status_code=403, detail="需要管理员权限") return user @app.delete("/api/users/{uid}") async def delete_user(uid: int, admin: dict = Depends(require_admin)): return {"deleted": uid} # ===== 登录接口:验证密码,返回 Token ===== class LoginRequest(BaseModel): email: str password: str @app.post("/api/auth/login") async def login(body: LoginRequest): user = await get_user_by_email(body.email) # 从数据库查用户 if not user or not verify_password(body.password, user.hashed_password): raise HTTPException(status_code=401, detail="邮箱或密码错误") token = create_access_token(user.id, user.role) return {"access_token": token, "token_type": "bearer"}

🔐 密码哈希(passlib bcrypt)

from passlib.context import CryptContext pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # 注册时:哈希密码存入数据库 def hash_password(plain_password: str) -> str: return pwd_context.hash(plain_password) # 登录时:验证密码 def verify_password(plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password) # ===== 使用示例 ===== hashed = hash_password("mypassword123") # "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW" is_valid = verify_password("mypassword123", hashed) # True is_valid = verify_password("wrongpassword", hashed) # False # ⚠️ 永远不要存明文密码!永远不要用 MD5/SHA1 做密码哈希! # bcrypt 内置 salt,相同密码每次 hash 结果不同,安全。

📋 速查:安全清单

项目要求
密码存储bcrypt 哈希,禁止明文或 MD5
JWT 密钥环境变量,≥32 字节随机字符串
HTTPS生产环境必须,防止 Token 被截获
Token 过期Access Token ≤ 30min,Refresh Token ≤ 7天
SQL 注入使用 ORM 参数化查询,禁止字符串拼接
CORS只允许已知域名,禁止 allow_origins=["*"]
错误信息不暴露内部错误详情(如数据库表名、堆栈)
Rate Limiting登录接口加频率限制防暴力破解
✏️ 填空题:验证 JWT Token
payload = jwt.(token, SECRET_KEY, algorithms=[])
🧠 小测验

JWT 和服务端 Session 的最大区别是什么?

📂 推荐目录结构

# ===== FastAPI 项目推荐结构 ===== # my-api/ # ├── pyproject.toml # 项目配置 # ├── requirements.txt # 依赖 # ├── .env # 环境变量(不提交!) # ├── alembic/ # 数据库迁移 # │ └── versions/ # ├── app/ # │ ├── __init__.py # │ ├── main.py # FastAPI 应用入口 # │ ├── config.py # 配置管理 # │ ├── dependencies.py # 共用依赖(认证等) # │ │ # │ ├── models/ # 数据模型(ORM/Pydantic) # │ │ ├── __init__.py # │ │ ├── user.py # User 表模型 # │ │ └── post.py # │ │ # │ ├── schemas/ # 请求/响应模型(Pydantic) # │ │ ├── __init__.py # │ │ ├── user.py # CreateUserRequest, UserResponse # │ │ └── post.py # │ │ # │ ├── services/ # 业务逻辑层 # │ │ ├── __init__.py # │ │ ├── user_service.py # 用户相关业务逻辑 # │ │ └── post_service.py # │ │ # │ ├── routers/ # 路由层(Controller) # │ │ ├── __init__.py # │ │ ├── user_router.py # 用户 API 路由 # │ │ └── post_router.py # │ │ # │ └── utils/ # 工具函数 # │ ├── __init__.py # │ └── security.py # JWT、密码哈希等 # │ # └── tests/ # ├── conftest.py # 测试配置和 fixture # ├── test_user_service.py # └── test_user_router.py

🏗️ 三层架构

Router 路由层

接收 HTTP 请求,提取参数,调用 Service,返回响应。不放业务逻辑。

类比前端:组件(只负责触发和展示)

Service 业务层

核心业务逻辑。校验、计算、编排多个数据操作。不直接处理 HTTP。

类比前端:hooks / store actions

Repository 数据层

数据库操作封装。CRUD 操作。不含业务逻辑。

类比前端:API 请求函数

# ===== 三层架构示例 ===== # --- schemas/user.py(数据模型)--- from pydantic import BaseModel class CreateUserRequest(BaseModel): name: str email: str class UserResponse(BaseModel): id: int name: str email: str # --- services/user_service.py(业务逻辑)--- class UserService: def create_user(self, data: CreateUserRequest) -> UserResponse: # 1. 检查邮箱是否已存在 # 2. 创建用户记录 # 3. 发送欢迎邮件(可选) # 4. 返回用户信息 return UserResponse(id=1, name=data.name, email=data.email) # --- routers/user_router.py(路由)--- from fastapi import APIRouter, Depends router = APIRouter(prefix="/api/users", tags=["Users"]) def get_user_service(): return UserService() @router.post("/", status_code=201, response_model=UserResponse) def create_user( data: CreateUserRequest, service: UserService = Depends(get_user_service), ): return service.create_user(data)

⚙️ 配置管理

# config.py —— 用 pydantic-settings 管理配置 from pydantic_settings import BaseSettings class Settings(BaseSettings): """从环境变量 / .env 文件读取配置""" app_name: str = "My API" debug: bool = False database_url: str = "sqlite:///./app.db" secret_key: str = "change-me-in-production" cors_origins: list[str] = ["http://localhost:3000"] model_config = {"env_file": ".env"} settings = Settings() # .env 文件: # DATABASE_URL=postgresql://user:pass@localhost/mydb # SECRET_KEY=super-secret-key # DEBUG=true
💡 核心原则:依赖方向是单向的——Router → Service → Repository。上层可以调用下层,下层不能调用上层。这和前端的"组件 → hooks → API"分层完全一致。

📋 速查:项目结构对照

前端分层后端分层职责
components/routers/入口,处理请求/渲染
hooks/ / stores/services/业务逻辑
api/repositories/数据访问
types/schemas/数据模型定义
utils/utils/工具函数
.envconfig.py + .env配置管理
__tests__/tests/测试

🗺️ 技术栈关系图

层次技术职责学习优先级
编程语言Python业务逻辑实现⭐⭐⭐⭐⭐ 第一步
Web 框架FastAPIHTTP 请求处理、路由、中间件⭐⭐⭐⭐⭐ 第二步
数据校验Pydantic请求/响应数据校验⭐⭐⭐⭐⭐ 和 FastAPI 一起学
关系数据库MySQL / PostgreSQL持久化数据存储⭐⭐⭐⭐⭐ 第三步
ORMSQLAlchemy / TortoisePython 对象映射数据库表⭐⭐⭐⭐ 跟数据库一起学
缓存Redis热数据缓存、会话、限流⭐⭐⭐⭐ 第四步
认证JWT / OAuth2用户身份验证⭐⭐⭐⭐ 项目必备
任务队列Celery / RQ异步任务(发邮件、生成报表)⭐⭐⭐ 进阶
容器化Docker环境一致性、部署⭐⭐⭐⭐ 第五步
CI/CDGitHub Actions自动测试、自动部署⭐⭐⭐ 进阶
监控Prometheus / Grafana性能监控、告警⭐⭐ 高级

📐 推荐学习路径

Phase 1:语言 + 框架(本课程)

  • Python 语法和数据结构
  • FastAPI + Pydantic
  • pytest 测试
  • 能写 CRUD API

Phase 2:数据库

  • MySQL 表设计和 SQL
  • 索引和查询优化
  • SQLAlchemy ORM
  • 数据库迁移(Alembic)

Phase 3:缓存 + 认证

  • Redis 数据结构
  • 缓存策略
  • JWT 认证
  • 权限控制 RBAC

Phase 4:部署 + 运维

  • Docker 容器化
  • docker-compose 编排
  • CI/CD 流水线
  • 日志和监控
💡 学习建议:不要试图同时学所有东西。按照 Phase 1 → 2 → 3 → 4 的顺序,每个阶段做一个实际项目巩固。本课程覆盖 Phase 1 的全部内容。

📋 速查:后端技术栈选型

需求推荐方案替代方案
Web 框架FastAPIDjango, Flask
关系数据库PostgreSQLMySQL
ORMSQLAlchemy 2.0Tortoise ORM
缓存RedisMemcached
认证python-jose (JWT)authlib
任务队列CeleryRQ, Dramatiq
数据库迁移AlembicDjango migrations
测试pytestunittest
代码格式化RuffBlack + isort
容器化DockerPodman

🎯 项目:个人博客 API

选择博客系统是因为它包含了后端核心场景:用户认证、CRUD、关联查询、分页、权限控制。

V1 —— 基础 CRUD(第 1 周)

V1 任务清单
  1. 搭建 FastAPI 项目结构(router/service/schema 分层)
  2. 实现用户注册/登录接口(密码哈希、JWT)
  3. 实现文章 CRUD(创建/列表/详情/更新/删除)
  4. 实现评论 CRUD(关联文章)
  5. 统一响应格式 {success, data, error}
  6. 用 pytest 写 10+ 个测试

V2 —— 工程化(第 2 周)

V2 任务清单
  1. 接入 MySQL/PostgreSQL 数据库
  2. 用 SQLAlchemy 定义表模型
  3. 用 Alembic 管理数据库迁移
  4. 实现分页查询(page/limit)
  5. 实现权限控制(只有作者能编辑自己的文章)
  6. 添加请求参数校验(Pydantic 完整校验)
  7. 配置 CORS 和全局异常处理

V3 —— 生产就绪(第 3-4 周)

V3 任务清单
  1. Docker 化部署
  2. Redis 缓存热门文章
  3. 文件上传(用户头像)
  4. Rate Limiting 限流
  5. Logging 日志系统
  6. CI/CD 配置(GitHub Actions)
  7. API 文档完善
  8. 性能优化(N+1 查询检测)

✅ 验收标准

版本验收标准
V1能在 Postman/curl 中完成所有 CRUD 操作,测试覆盖率 > 60%
V2数据落数据库,分页正常,权限控制生效,测试覆盖率 > 80%
V3Docker 一键启动,有缓存、有日志、有 CI/CD,能演示完整流程
🎯 立即开始 V1
现在就创建项目目录,搭建 FastAPI 脚手架,实现第一个 GET / 接口。不要拖延,先让项目能跑起来。
查看参考思路
# 创建项目 mkdir blog-api && cd blog-api python -m venv venv source venv/bin/activate pip install fastapi "uvicorn[standard]" pydantic # 创建目录结构 mkdir -p app/{routers,services,schemas} touch app/__init__.py app/main.py touch app/routers/__init__.py touch app/services/__init__.py touch app/schemas/__init__.py # app/main.py from fastapi import FastAPI app = FastAPI(title="Blog API", version="0.1.0") @app.get("/") def root(): return {"message": "Blog API is running!"} # 运行:uvicorn app.main:app --reload

📝 课程考试

考试内容

  • Python 语法基础(30%)
  • 数据结构与算法(20%)
  • 面向对象编程(15%)
  • FastAPI / Pydantic(20%)
  • 工程能力(15%)

考试形式

  • 选择题 + 填空题 + 代码题
  • 时间限制:90 分钟
  • 及格线:70 分
  • 可以查看本课程内容

🔄 复盘建议

第一次不及格?

完全正常!回看错题对应的章节,重做练习题。重点是理解"为什么",不是背答案。

及格但不满意?

检查薄弱章节,特别关注有标记的错题。用代码编辑器实际跑一遍每个知识点。

高分通过?

恭喜!现在应该立即开始实战项目 V1。理论 + 实践双轮驱动才能真正掌握。

🎯 冲刺清单

考前复习重点
  1. 重做每节课的填空题和小测验
  2. 确保能默写:类定义、装饰器、Pydantic 模型
  3. 手写一个 FastAPI CRUD(不看文档)
  4. 理解三层架构的依赖方向
  5. 能说出 5 个 Python vs JS 的核心差异
🎉 完成课程的你已经具备了 Python 后端开发的基础能力。接下来学数据库、Redis、Docker,一步步成为全栈工程师!
返回学习地图总入口