Python OOP 与 FastAPI 核心机制
一、 Python 面向对象基础
1.1 类的定义与 pass 占位
在 Python 中,使用 class 关键字定义类。class 类名: 后面必须缩进并跟一个代码块。如果当前不需要写任何属性或方法,必须使用 pass 占位。
1 | class Person: |
注意:pass 没有任何实际功能,仅仅是为了防止 IndentationError(缩进错误)。如果不写 pass 且不写其他代码,程序会报错。
1.2 self 与实例变量初始化
Python 中的 self 相当于 Java 中的 this,代表当前操作的对象实例。
与 Java 的核心区别:
- 必须显式声明:
self必须是实例方法的第一个参数(如def __init__(self, name):),调用时不需要手动传,Python 会自动把对象传入。 - 名字只是约定:
self不是关键字,写成this或abc语法上也合法,但强烈不建议。 __init__是初始化方法:创建对象后自动调用,主要用于设置实例的初始属性。
1 | class Person: |
1.3 变量的作用域:局部变量、实例变量与类变量
这是极易混淆的地方,通过位置和前缀来区分:
| 变量类型 | 定义位置 | 前缀 | 归属 | 生命周期 |
|---|---|---|---|---|
| 局部变量 | 方法内部 | 无 | 当前方法 | 方法执行完即销毁 |
| 实例变量 | 方法内部(通常在 __init__) |
self. |
当前实例对象 | 随对象存在 |
| 类变量 | 类内部,方法外部 | 无 | 整个类(所有实例共享) | 随类存在 |
1 | class Person: |
1.4 动态添加属性与 __slots__
Python 是动态语言,允许在类外部或方法外动态给对象添加属性,即使类里没有定义。
1 | class Dog: |
注意:这是 Python 的灵活性所在,但也容易导致代码混乱。同一个类的不同对象可能拥有不同的属性。如果不希望外部随意添加,可以使用 __slots__ 限制。
1 | class Dog: |
二、 Python 三大特性与抽象类
2.1 封装(Encapsulation)
Python 没有像 Java 那样的 private 关键字,权限控制靠命名约定:
name:公有,谁都能访问。_name:受保护(约定俗成,外部最好别直接访问)。__name:私有。Python 底层会将其名字改写为_类名__name,起到隐藏效果。
1 | class Person: |
2.2 继承(Inheritance)
- 语法:
class 子类(父类): - 调用父类方法:使用
super().__init__(...)。 - 多继承:Python 支持多继承(如
class Duck(Flyable, Swimmable):)。如果多个父类有同名方法,Python 使用 MRO(方法解析顺序)算法决定调用哪个。
2.3 多态(Polymorphism)与鸭子类型
Python 的多态基于“鸭子类型”:不要求对象有继承关系,只要它有对应的方法,就能被调用。
1 | class Dog: |
类型注解与多态:即使使用类型注解(如 def func(animal: Animal)),运行时也会忽略,传入无关对象(如 Robot)依然能运行。类型注解只是给 IDE 和静态检查工具(如 mypy)看的提示,不是强制的。
2.4 抽象类与抽象方法
Python 通过 abc 模块实现抽象类。
1 | from abc import ABC, abstractmethod |
- 生效条件:必须同时满足“继承
ABC”和“包含@abstractmethod”。缺一不可。 - 实例化限制:子类如果没有完全实现抽象方法,代码不报错,但执行到
子类()时,会抛出TypeError。 - 与 Java 的区别:Java 在编译期强制要求子类实现或声明为
abstract;Python 在运行实例化时动态拦截。
三、 Python 异常处理基础
3.1 主动抛出异常(raise)
Python 使用 raise 关键字主动抛出异常(对应 Java 的 throw)。
1 | raise ValueError("年龄不能为负数") |
3.2 自定义异常
1 | class BusinessException(Exception): |
3.3 异常链(raise ... from ...)
from 的核心作用是建立异常链,保留原始异常(根因)。
如果不加 from e,原始异常信息可能会被掩盖;加上后,Python 会把两个异常串联打印,明确告诉开发者:“因为发生了 DatabaseError,所以导致了 BusinessException”。
1 | try: |
与 Java 的对比:
- Java:通过构造函数传递原始异常,如
throw new BusinessException(500, "查询失败", e); - Python:通过
raise ... from e语法显式声明因果。
进阶用法:from None
如果不想在日志或前端暴露底层原始异常(如数据库表名),可以使用 raise BusinessException(...) from None,彻底隐藏原始异常。
3.4 资源清理与 finally
finally 块的作用是:无论 try 块里是否发生异常,finally 块里的代码一定会执行。 它最经典的用途是释放资源(如关闭文件、关闭数据库连接)。
1 | f = open("test.txt", "r") |
在 FastAPI 中的最佳实践:
在 FastAPI 项目中,不建议在业务代码里手动写 finally 关闭数据库连接。因为 FastAPI 通常使用依赖注入(Depends)来管理数据库会话的生命周期,框架会在请求结束后自动处理 close。如果在业务逻辑里强行 finally: db.close(),可能会导致框架后续操作(如 commit/rollback)报错(连接已关闭)。只有在手动开启了底层资源(如原生文件流)时,才需要配合 finally 使用。
四、 FastAPI 基础:参数接收
4.1 路由映射
FastAPI 使用装饰器绑定 URL 和函数。
1 | # 相当于 Java 的 @GetMapping("/follow") |
4.2 三种参数的自动推断
FastAPI 根据参数名、类型注解和默认值自动识别参数来源,不需要像 Java 那样写 @RequestParam。
| 参数类型 | 识别规则 | 示例 |
|---|---|---|
| 路径参数 | 名字和路径 {} 里一致 |
def get_user(id: int) |
| 查询参数 | 不在路径里,且类型不是 Pydantic 模型 | def search(keyword: str, page: int = 1) |
| 请求体 | 参数类型继承自 BaseModel |
def create(user: UserDTO) |
4.3 Pydantic 模型(BaseModel)
继承自 pydantic.BaseModel 的类就是 Pydantic 模型,FastAPI 会自动从请求体(JSON)解析数据并进行校验。
1 | from pydantic import BaseModel |
核心特性:
- 默认值即选填:没有默认值的字段必填,传错类型或缺失会返回 422 错误。
- 自动类型转换:传
"18"会自动转成整数18。 - 与普通类的区别:不需要手写
__init__,Pydantic 自动生成,并带有强校验逻辑。
五、 FastAPI 基础:返回值与响应
5.1 自动转 JSON
FastAPI 默认就是 @RestController 风格。你只管 return Python 对象(字典、Pydantic 模型、列表),框架会自动序列化成 JSON,并设置正确的 Content-Type。
1 |
|
5.2 使用 response_model 过滤字段
如果返回的是数据库对象(包含密码等敏感信息),可以使用 response_model 过滤并校验。
1 | class UserDTO(BaseModel): |
5.3 自定义响应(JSONResponse)
如果需要自定义 HTTP 状态码和响应头,需要使用 JSONResponse。
1 | from fastapi.responses import JSONResponse |
与 Java 统一返回格式的对比:Java 习惯返回 Result<T> 对象(HTTP 永远 200,业务状态码在 JSON 里)。FastAPI 推荐直接用 HTTP 状态码表示结果。如果需要模拟 Java 风格,可以自定义 Result 模型或修改全局异常处理器。
六、 FastAPI 异常处理与全局配置
6.1 抛出 HTTPException
在 Web 层,使用 HTTPException 主动抛出 HTTP 错误。
1 | from fastapi import HTTPException, status |
status_code:HTTP 状态码(必填)。detail:错误详情,默认会被包装成{"detail": "..."}返回给前端。headers:自定义响应头(选填)。
6.2 全局兜底异常处理器
使用 @app.exception_handler(Exception) 捕获未预料的异常(如空指针、数据库断开),记录日志并返回统一的 500 错误。
1 |
|
6.3 覆盖 HTTPException 处理器(关键踩坑)
HTTPException 默认不会被 @app.exception_handler(Exception) 捕获,它走的是 FastAPI 内置的处理器。
如果要自定义 HTTPException 的返回格式(如改成 {"code": ..., "message": ...}),必须显式覆盖:
1 |
|
异常处理器优先级:
- 特定的自定义异常处理器(如
BusinessException) - 内置处理器(
HTTPException、RequestValidationError) - 兜底处理器(
Exception)
七、 Python 异步编程与 FastAPI 并发模型
7.1 异步编程的本质(与 Java 多线程的区别)
在 Java 中,异步通常依赖多线程(如 CompletableFuture、Thread),由操作系统调度,主线程不阻塞,继续执行。这属于广义异步。
Python 的 async/await 是基于**单线程 + 事件循环(Event Loop)**的协程模型。
- 遇到
await时,当前协程主动挂起,把 CPU 让给事件循环去执行其他任务。 - 等 I/O 完成后,事件循环再回来恢复执行。
- 没有线程上下文切换开销,一个线程可支撑数万并发。
通俗比喻:
- Java 多线程异步:餐厅雇了 10 个服务员(10 个线程),每人服务一桌,等菜时服务员就站着干等。
- Python 协程异步:餐厅只有 1 个服务员(单线程),给 A 桌点完菜立刻去 B 桌,谁的菜好了就去端给谁,一个人跑全场。
| 维度 | Java 异步(多线程) | Python 异步(协程) |
|---|---|---|
| 底层机制 | 多线程 / 线程池 | 单线程 + 事件循环 |
| 调度者 | 操作系统(抢占式调度) | Python 解释器(协作式调度) |
| 切换开销 | 大(线程上下文切换) | 极小(函数挂起与恢复) |
| 内存占用 | 每个线程约 1MB | 每个协程约几KB |
| 并发能力 | 几百到几千 | 几万到几十万 |
| 适用场景 | CPU 密集、I/O 密集 | 高并发 I/O 密集 |
7.2 事件循环(Event Loop)核心机制
事件循环是异步编程的“总调度中心”,它是一个死循环,不断在任务队列里找活干。
协作式调度原则:
- 遇到
await:协程主动挂起,让出 CPU,事件循环去执行其他任务。等 I/O 完成后再唤醒该协程继续执行。 - 没有遇到
await:协程独占 CPU,事件循环被阻塞,其他所有任务全部卡死等待。 - 只要写了
await,无论耗时长短,都会挂起(哪怕只是挂起一瞬间)。
FastAPI 中事件循环的双重角色:
| 身份 | 处理对象 | 具体行为 |
|---|---|---|
| 执行者 | async def 路由 |
自己亲自在单线程里跑。遇到 await 就挂起,切换下一个协程。 |
| 调度者 | 普通 def 路由 |
自己不跑,直接丢给外部线程池(Threadpool),并继续在大厅接客。 |
7.3 同步阻塞 vs 异步非阻塞
同步阻塞代码:按顺序执行,遇到耗时操作就死等,卡住当前线程。
- 典型例子:
time.sleep(5)、requests.get(url)、pymysql、psycopg2。
异步非阻塞代码:遇到耗时操作就挂起,让出控制权。
- 典型例子:
await asyncio.sleep(5)、await httpx.AsyncClient().get(url)、asyncpg。
FastAPI 中的致命陷阱:
在 async def 路由中绝对不能写同步阻塞代码。如果写了 time.sleep(10),这 10 秒内整个事件循环会被卡死,其他所有请求都要排队等待,服务看起来就像死机了一样。
7.4 FastAPI 并发模型实战
写法一:async def(推荐,高性能)
路由直接跑在事件循环里。只要库是异步的(httpx、asyncpg),遇到 await 就会挂起让出 CPU,并发能力极强。
1 |
|
写法二:普通 def(兼容同步库)
FastAPI 识别到是同步路由,会自动把它丢进外部线程池(默认约 40 个线程),不会卡死事件循环。
1 |
|
写法三:在 async def 中安全地调用同步阻塞代码
如果必须在异步路由中使用同步库,必须用 asyncio.to_thread 将其丢到外部线程池,避免卡死事件循环。
1 | import asyncio |
7.5 Python 多线程与 GIL(全局解释器锁)
Python 有 threading 模块,但由于 **GIL(全局解释器锁)**的存在,在同一个 Python 进程内,同一时刻只允许一个线程执行 Python 字节码。
- CPU 密集型任务:多线程不仅不能加速,反而会因为线程切换和抢锁变慢。
- I/O 密集型任务:当线程遇到 I/O 操作时会主动释放 GIL,此时其他线程可以拿到 GIL 执行,所以多线程在 I/O 场景下依然有效。
- 绕过 GIL:如果需要利用多核做 CPU 密集型计算,通常使用多进程(
multiprocessing),每个进程有独立的解释器和 GIL。
学习建议:当前阶段无需深学 threading。FastAPI 的 async/await 已覆盖绝大多数并发需求;同步 def 路由 FastAPI 会自动走线程池。等遇到 CPU 密集任务或维护老项目时,再回头补充多线程/多进程知识。
7.6 FastAPI 并发模型总结对比
| 场景 | Java (Spring MVC/Tomcat) | FastAPI (async def) |
FastAPI (def) |
|---|---|---|---|
| 并发单位 | 线程(Thread) | 协程(Coroutine) | 线程(Threadpool) |
| 底层机制 | 线程池,每请求一线程 | 单线程 + 事件循环 | 线程池,默认约 40 线程 |
| 同时处理 100 个请求 | 创建 100 个线程 | 1 个线程内疯狂切换协程 | 40 个线程干活,60 个排队 |
| 并发上限 | 约 200(线程池上限) | 几万到几十万 | 默认 40(可调) |
| 适合场景 | 传统阻塞 I/O | 高并发异步 I/O | 兼容同步阻塞库 |
实战建议:
- 首选
async def,搭配异步库(httpx、asyncpg),发挥高并发优势。 - 遇到同步库(
requests、pymysql),老老实实写def路由,交给 FastAPI 丢进线程池。 - 千万不要在
async def里直接写同步阻塞代码,否则整个服务会被卡死。
今日总结
| 模块 | 核心要点 |
|---|---|
| Python OOP 基础 | pass 仅占位;self 显式代表实例;局部变量无前缀,实例变量带 self.;类变量写在方法外。 |
| 动态特性 | 允许动态添加属性(慎用),可用 __slots__ 限制;类型注解非强制,运行时忽略。 |
| 抽象类 | 需同时继承 ABC 并包含 @abstractmethod;实例化时报错,而非编译期。 |
| 异常处理 | raise 主动抛异常;from e 建立异常链保留根因;finally 用于资源清理;FastAPI 中优先依赖注入而非手动 finally。 |
| FastAPI 参数 | 名字和类型自动推断路径、查询参数;继承 BaseModel 自动识别请求体并校验。 |
| FastAPI 响应 | 直接 return 自动转 JSON;response_model 过滤字段;JSONResponse 自定义状态码。 |
| 异常处理器 | HTTPException 不会被 Exception 兜底捕获;覆盖处理器需要显式注册;处理优先级从具体到通用。 |
| 异步编程 | 单线程 + 事件循环(协作式调度);遇到 await 才挂起,否则阻塞;async def 中绝不能写同步阻塞代码。 |
| 并发模型 | 事件循环是执行者(执行 async def)兼调度者(将 def 丢给线程池);Python 多线程受 GIL 限制,只适合 I/O 密集场景。 |
| Java 对比 | Java 编译期强检查、强类型、多线程真并行;Python 运行期动态检查、鸭子类型、单线程协程高并发。Java 习惯统一 Result,FastAPI 推荐原生 HTTP 语义。 |