04 Python OOP 与 FastAPI 核心机制学习笔记

Python OOP 与 FastAPI 核心机制

一、 Python 面向对象基础

1.1 类的定义与 pass 占位

在 Python 中,使用 class 关键字定义类。
class 类名: 后面必须缩进并跟一个代码块。如果当前不需要写任何属性或方法,必须使用 pass 占位。

1
2
class Person:
pass # 仅占位,保证语法正确

注意:pass 没有任何实际功能,仅仅是为了防止 IndentationError(缩进错误)。如果不写 pass 且不写其他代码,程序会报错。

1.2 self 与实例变量初始化

Python 中的 self 相当于 Java 中的 this,代表当前操作的对象实例。

与 Java 的核心区别:

  1. 必须显式声明:self 必须是实例方法的第一个参数(如 def __init__(self, name):),调用时不需要手动传,Python 会自动把对象传入。
  2. 名字只是约定:self 不是关键字,写成 this 或 abc 语法上也合法,但强烈不建议。
  3. __init__ 是初始化方法:创建对象后自动调用,主要用于设置实例的初始属性。
1
2
3
4
5
6
7
class Person:
def __init__(self, name, age):
self.name = name # 实例变量
self.age = age

p = Person("小明", 18)
print(p.name) # 输出 小明

1.3 变量的作用域:局部变量、实例变量与类变量

这是极易混淆的地方,通过位置和前缀来区分:

变量类型 定义位置 前缀 归属 生命周期
局部变量 方法内部 无 当前方法 方法执行完即销毁
实例变量 方法内部(通常在 __init__) self. 当前实例对象 随对象存在
类变量 类内部,方法外部 无 整个类(所有实例共享) 随类存在
1
2
3
4
5
6
class Person:
species = "人类" # 类变量(相当于 Java 的 static 变量)

def __init__(self, name):
self.name = name # 实例变量
temp = "临时" # 局部变量,方法结束就没了

1.4 动态添加属性与 __slots__

Python 是动态语言,允许在类外部或方法外动态给对象添加属性,即使类里没有定义。

1
2
3
4
5
6
class Dog:
pass

d = Dog()
d.name = "旺财" # 动态添加属性
d.age = 3

注意:这是 Python 的灵活性所在,但也容易导致代码混乱。同一个类的不同对象可能拥有不同的属性。如果不希望外部随意添加,可以使用 __slots__ 限制。

1
2
3
4
5
6
class Dog:
__slots__ = ('name', 'age') # 只允许这两个属性

d = Dog()
d.name = "旺财" # 允许
d.color = "白" # 报错:AttributeError

二、 Python 三大特性与抽象类

2.1 封装(Encapsulation)

Python 没有像 Java 那样的 private 关键字,权限控制靠命名约定:

  • name:公有,谁都能访问。
  • _name:受保护(约定俗成,外部最好别直接访问)。
  • __name:私有。Python 底层会将其名字改写为 _类名__name,起到隐藏效果。
1
2
3
4
5
class Person:
def __init__(self, name, age, salary):
self.name = name # 公有
self._age = age # 受保护
self.__salary = salary # 私有

2.2 继承(Inheritance)

  • 语法:class 子类(父类):
  • 调用父类方法:使用 super().__init__(...)。
  • 多继承:Python 支持多继承(如 class Duck(Flyable, Swimmable):)。如果多个父类有同名方法,Python 使用 MRO(方法解析顺序)算法决定调用哪个。

2.3 多态(Polymorphism)与鸭子类型

Python 的多态基于“鸭子类型”:不要求对象有继承关系,只要它有对应的方法,就能被调用。

1
2
3
4
5
6
7
8
9
10
11
class Dog:
def speak(self): print("汪汪")

class Robot:
def speak(self): print("滴滴")

def make_speak(obj): # 不需要类型约束
obj.speak()

make_speak(Dog()) # 汪汪
make_speak(Robot()) # 滴滴

类型注解与多态:即使使用类型注解(如 def func(animal: Animal)),运行时也会忽略,传入无关对象(如 Robot)依然能运行。类型注解只是给 IDE 和静态检查工具(如 mypy)看的提示,不是强制的。

2.4 抽象类与抽象方法

Python 通过 abc 模块实现抽象类。

1
2
3
4
5
6
from abc import ABC, abstractmethod

class Animal(ABC): # 继承 ABC,激活抽象机制
@abstractmethod
def speak(self): # 打上抽象标签
pass
  • 生效条件:必须同时满足“继承 ABC”和“包含 @abstractmethod”。缺一不可。
  • 实例化限制:子类如果没有完全实现抽象方法,代码不报错,但执行到 子类() 时,会抛出 TypeError。
  • 与 Java 的区别:Java 在编译期强制要求子类实现或声明为 abstract;Python 在运行实例化时动态拦截。

三、 Python 异常处理基础

3.1 主动抛出异常(raise)

Python 使用 raise 关键字主动抛出异常(对应 Java 的 throw)。

1
raise ValueError("年龄不能为负数")

3.2 自定义异常

1
2
3
4
5
class BusinessException(Exception):
def __init__(self, code: int, message: str):
self.code = code
self.message = message
super().__init__(message)

3.3 异常链(raise ... from ...)

from 的核心作用是建立异常链,保留原始异常(根因)。

如果不加 from e,原始异常信息可能会被掩盖;加上后,Python 会把两个异常串联打印,明确告诉开发者:“因为发生了 DatabaseError,所以导致了 BusinessException”。

1
2
3
4
5
try:
user = db.query(user_id)
except DatabaseError as e:
# 👇 显式建立异常链
raise BusinessException(500, "查询失败") from e

与 Java 的对比:

  • Java:通过构造函数传递原始异常,如 throw new BusinessException(500, "查询失败", e);
  • Python:通过 raise ... from e 语法显式声明因果。

进阶用法:from None
如果不想在日志或前端暴露底层原始异常(如数据库表名),可以使用 raise BusinessException(...) from None,彻底隐藏原始异常。

3.4 资源清理与 finally

finally 块的作用是:无论 try 块里是否发生异常,finally 块里的代码一定会执行。 它最经典的用途是释放资源(如关闭文件、关闭数据库连接)。

1
2
3
4
5
6
7
f = open("test.txt", "r")
try:
content = f.read()
except FileNotFoundError as e:
print("文件不存在")
finally:
f.close() # 👈 不管有没有报错,文件都要关!

在 FastAPI 中的最佳实践:
在 FastAPI 项目中,不建议在业务代码里手动写 finally 关闭数据库连接。因为 FastAPI 通常使用依赖注入(Depends)来管理数据库会话的生命周期,框架会在请求结束后自动处理 close。如果在业务逻辑里强行 finally: db.close(),可能会导致框架后续操作(如 commit/rollback)报错(连接已关闭)。只有在手动开启了底层资源(如原生文件流)时,才需要配合 finally 使用。


四、 FastAPI 基础:参数接收

4.1 路由映射

FastAPI 使用装饰器绑定 URL 和函数。

1
2
3
@app.get("/follow")  # 相当于 Java 的 @GetMapping("/follow")
def read_follow():
return {"message": "关注成功"}

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
2
3
4
5
6
7
from pydantic import BaseModel

class ChatRequest(BaseModel):
session_id: str # 必填
user_message: str # 必填
nick_name: str = "小美" # 选填,有默认值
nature: str = "温柔可爱的台湾腔姑娘" # 选填

核心特性:

  • 默认值即选填:没有默认值的字段必填,传错类型或缺失会返回 422 错误。
  • 自动类型转换:传 "18" 会自动转成整数 18。
  • 与普通类的区别:不需要手写 __init__,Pydantic 自动生成,并带有强校验逻辑。

五、 FastAPI 基础:返回值与响应

5.1 自动转 JSON

FastAPI 默认就是 @RestController 风格。你只管 return Python 对象(字典、Pydantic 模型、列表),框架会自动序列化成 JSON,并设置正确的 Content-Type。

1
2
3
@app.get("/user")
def get_user():
return {"name": "小明", "age": 18} # 自动转 JSON

5.2 使用 response_model 过滤字段

如果返回的是数据库对象(包含密码等敏感信息),可以使用 response_model 过滤并校验。

1
2
3
4
5
6
7
class UserDTO(BaseModel):
name: str
age: int

@app.get("/user", response_model=UserDTO)
def get_user():
return {"name": "小明", "age": 18, "password": "123456"} # 密码会被自动过滤

5.3 自定义响应(JSONResponse)

如果需要自定义 HTTP 状态码和响应头,需要使用 JSONResponse。

1
2
3
4
5
6
7
from fastapi.responses import JSONResponse

return JSONResponse(
status_code=404,
content={"code": 404, "message": "不存在", "data": None},
headers={"X-Custom": "value"}
)

与 Java 统一返回格式的对比:Java 习惯返回 Result<T> 对象(HTTP 永远 200,业务状态码在 JSON 里)。FastAPI 推荐直接用 HTTP 状态码表示结果。如果需要模拟 Java 风格,可以自定义 Result 模型或修改全局异常处理器。


六、 FastAPI 异常处理与全局配置

6.1 抛出 HTTPException

在 Web 层,使用 HTTPException 主动抛出 HTTP 错误。

1
2
3
4
5
6
7
from fastapi import HTTPException, status

raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="用户不存在",
headers={"WWW-Authenticate": "Bearer"}
)
  • status_code:HTTP 状态码(必填)。
  • detail:错误详情,默认会被包装成 {"detail": "..."} 返回给前端。
  • headers:自定义响应头(选填)。

6.2 全局兜底异常处理器

使用 @app.exception_handler(Exception) 捕获未预料的异常(如空指针、数据库断开),记录日志并返回统一的 500 错误。

1
2
3
4
@app.exception_handler(Exception)
async def exception_handler(request, exc):
logger.error(f"发生异常: {exc}")
return JSONResponse(status_code=500, content={"code": 500, "message": "服务器内部错误"})

6.3 覆盖 HTTPException 处理器(关键踩坑)

HTTPException 默认不会被 @app.exception_handler(Exception) 捕获,它走的是 FastAPI 内置的处理器。

如果要自定义 HTTPException 的返回格式(如改成 {"code": ..., "message": ...}),必须显式覆盖:

1
2
3
4
5
6
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={"code": exc.status_code, "message": exc.detail, "data": None}
)

异常处理器优先级:

  1. 特定的自定义异常处理器(如 BusinessException)
  2. 内置处理器(HTTPException、RequestValidationError)
  3. 兜底处理器(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
2
3
4
5
@app.get("/async")
async def async_endpoint():
async with httpx.AsyncClient() as client:
resp = await client.get("https://api.com") # 挂起,让出控制权
return resp.json()

写法二:普通 def(兼容同步库)
FastAPI 识别到是同步路由,会自动把它丢进外部线程池(默认约 40 个线程),不会卡死事件循环。

1
2
3
4
@app.get("/sync")
def sync_endpoint():
time.sleep(3) # 阻塞的是线程池里的线程,不会卡死事件循环
return {"message": "done"}

写法三:在 async def 中安全地调用同步阻塞代码
如果必须在异步路由中使用同步库,必须用 asyncio.to_thread 将其丢到外部线程池,避免卡死事件循环。

1
2
3
4
5
6
7
import asyncio

@app.get("/safe")
async def safe_endpoint():
# 👇 将同步阻塞代码丢到外部线程池
result = await asyncio.to_thread(time.sleep, 10)
return {"message": "done"}

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 兼容同步阻塞库

实战建议:

  1. 首选 async def,搭配异步库(httpx、asyncpg),发挥高并发优势。
  2. 遇到同步库(requests、pymysql),老老实实写 def 路由,交给 FastAPI 丢进线程池。
  3. 千万不要在 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 语义。