阶段3(下):RAG、向量数据库专题;接入 BOSS直聘调研(research/、岗位对照和延伸技术栈);新增 cs 领域和 12 个技术栈骨架;面试题全部补齐追问并加入校验
This commit is contained in:
1 parent
7f52513ffc
commit
6650f7eb6d
52 files changed
+2777
-38
No files matched your search
@@ -18,42 +18,62 @@ week: [4, 16]
|
||||
|
||||
**参考答案**:进程有独立的内存空间,切换和通信开销大,能利用多核;线程共享进程内存,由操作系统抢占式调度,在 CPython 中受 GIL 限制不能并行执行 Python 代码;协程是用户态的、协作式调度的执行单元,在 `await` 处主动让出控制权,切换开销极小,单线程即可同时处理大量 IO 等待。
|
||||
|
||||
**追问**:Python 多线程适合什么场景?→ I/O 密集且必须使用同步库的场景;CPU 密集任务因为 GIL 用多进程。
|
||||
|
||||
### 【基础】asyncio 适合什么场景?不适合什么场景?
|
||||
|
||||
**参考答案**:适合 IO 密集、并发连接多的场景:调用大模型 API、HTTP 请求、数据库查询、WebSocket / SSE 长连接。不适合 CPU 密集计算(单线程,无法加速),这类任务用多进程或交给 C 扩展库。
|
||||
|
||||
**追问**:CPU 密集的任务混在异步服务里怎么办?→ 用 `loop.run_in_executor` 放到进程池,或者交给单独的任务队列 / 服务处理,避免阻塞事件循环。
|
||||
|
||||
### 【基础】调用 `async def` 函数但没有 `await` 会怎样?
|
||||
|
||||
**参考答案**:只会创建协程对象,函数体不会执行,Python 会给出 “coroutine was never awaited” 警告。这一点和 JS 不同——JS 的 async 函数调用后立即开始执行。
|
||||
|
||||
**追问**:怎么发现忘记 await 的问题?→ 运行时会出现 `coroutine was never awaited` 的警告;类型检查工具(mypy / pyright)和 linter 也能检查出来。
|
||||
|
||||
### 【基础】为什么在协程里不能使用 `time.sleep` 或 `requests`?
|
||||
|
||||
**参考答案**:事件循环是单线程的,阻塞调用会让整个线程停住,所有其他协程都无法运行,服务的并发能力退化为 1。应该用 `asyncio.sleep`、`httpx.AsyncClient` 等异步库;只能用同步库时用 `asyncio.to_thread` 放到线程池执行。
|
||||
|
||||
**追问**:必须使用同步库怎么办?→ 用 `asyncio.to_thread()` 放到线程池执行,或者换成异步库(`httpx.AsyncClient`、`asyncpg` 等)。
|
||||
|
||||
## 进阶
|
||||
|
||||
### 【进阶】`asyncio.gather` 和 `TaskGroup` 有什么区别?
|
||||
|
||||
**参考答案**:`gather` 在某个任务抛异常时立即把异常抛给调用方,但其余任务继续运行;加 `return_exceptions=True` 则把异常作为结果返回。`TaskGroup`(3.11+)是结构化并发:一个任务失败时取消组内其他任务,等它们全部结束后,以 `ExceptionGroup` 抛出所有异常,不会留下“孤儿任务”。
|
||||
|
||||
**追问**:`gather` 的 `return_exceptions=True` 有什么用?→ 一个任务异常时不会立即抛出,而是把异常作为结果返回,适合“部分失败可以接受”的批量调用。
|
||||
|
||||
### 【进阶】怎么限制并发数?
|
||||
|
||||
**参考答案**:用 `asyncio.Semaphore(n)`,在每个任务的实际调用外层 `async with sem:`;或者用固定数量的 worker 从 `asyncio.Queue` 取任务。注意多个请求要共享同一个 Semaphore;多进程 / 多实例时需要 Redis 等全局限流。
|
||||
|
||||
**追问**:信号量和限速有什么区别?→ 信号量限制同时进行的请求数;限速限制单位时间内的请求数(如每分钟 600 次),服务商通常两种都有限制,需要同时控制。
|
||||
|
||||
### 【进阶】asyncio 的取消机制是怎样的?
|
||||
|
||||
**参考答案**:`task.cancel()` 请求取消,任务在下一个 `await` 点收到 `CancelledError`。任务可以捕获它做清理(关闭连接、记录状态),但应该重新抛出,否则取消失效。`asyncio.timeout` / `wait_for` 超时也是通过取消内部任务实现的。`CancelledError` 继承自 `BaseException`,所以 `except Exception` 不会误吞它。
|
||||
|
||||
**追问**:捕获了 `CancelledError` 后应该怎么做?→ 做完清理后重新抛出;吞掉它会导致取消失效、`TaskGroup` / 超时无法正常工作。
|
||||
|
||||
## 场景题
|
||||
|
||||
### 【场景题】需要对 1 万条文本调用大模型生成摘要,服务商限制每分钟 600 次请求,怎么设计?
|
||||
|
||||
**参考答案**:用 Semaphore 或 worker 池限制并发,再加速率控制(令牌桶,每秒约 10 次);每次调用设超时;对 429、超时、5xx 做带抖动的指数退避重试,429 响应如果带 `Retry-After` 就按它等待;单条失败记录下来,最后汇总重跑;记录进度(已完成的 ID)以便中断后续跑;结果写入数据库而不是全放内存。规模更大时用消息队列分发给多个 worker。
|
||||
|
||||
**追问**:中途失败的任务怎么办?→ 每条记录保存处理状态,失败的记录按错误类型重试(限流错误退避重试,内容错误记录跳过),支持断点续跑。
|
||||
|
||||
### 【场景题】FastAPI 接口里调用了一个同步的 SDK,发现服务在压测时吞吐很低,为什么?怎么改?
|
||||
|
||||
**参考答案**:在 `async def` 路由里直接调用同步 SDK,阻塞了事件循环,所有请求排队。改法:换成 SDK 的异步版本;或者 `await asyncio.to_thread(sdk.call, ...)`;或者把路由定义为普通 `def`,FastAPI 会把它放到线程池执行。然后重新压测确认。
|
||||
|
||||
**追问**:`def` 路由和 `async def` 路由在这里有什么区别?→ `def` 路由会被 FastAPI 放到线程池执行,不会阻塞事件循环;`async def` 中直接调用同步 SDK 会阻塞整个事件循环。
|
||||
|
||||
### 【场景题】一个异步任务在服务关闭时总是卡住,可能是什么原因?
|
||||
|
||||
**参考答案**:任务里捕获了 `CancelledError` 却没有重新抛出(或者用了裸 `except:`),导致取消无效;或者任务在执行不响应取消的阻塞调用(同步 IO、`to_thread` 中的线程无法被强制停止);或者有后台任务没有被追踪和取消。修复:正确传播取消、给阻塞调用设超时、在应用的生命周期关闭阶段统一取消并等待后台任务。
|
||||
|
||||
**追问**:怎么给关闭过程设置上限?→ 用 `asyncio.timeout()` 或 `asyncio.wait_for()` 给关闭等待设置超时,超时后取消剩余任务并记录日志。
|
||||
@@ -18,42 +18,62 @@ week: [4, 5, 16]
|
||||
|
||||
**参考答案**:基于 Starlette(ASGI)和 pydantic,原生支持 async,适合 IO 密集的模型调用;通过类型标注自动完成参数解析和校验、生成 OpenAPI 文档;依赖注入方便组织鉴权、数据库会话和测试替换;支持流式响应和 WebSocket。AI 生态(模型 SDK、LangChain、向量库客户端)以 Python 为主,用 FastAPI 封装服务很自然。
|
||||
|
||||
**追问**:FastAPI 和 Flask、Django 怎么选?→ AI 服务、异步 I/O、需要自动接口文档时选 FastAPI;需要完整后台管理和 ORM 的传统业务系统可以选 Django。
|
||||
|
||||
### 【基础】FastAPI 怎么区分路径参数、查询参数和请求体?
|
||||
|
||||
**参考答案**:在路径模板中出现的参数是路径参数;简单类型(int、str、bool 等)的其他参数是查询参数;pydantic 模型类型的参数是请求体;请求头、Cookie 用 `Header()`、`Cookie()` 显式声明。也可以用 `Path()`、`Query()`、`Body()` 显式指定并添加校验规则。
|
||||
|
||||
**追问**:请求体可以是多个 Pydantic 模型吗?→ 可以,多个模型参数会按参数名嵌套在 JSON 中;单个模型参数想嵌套时用 `Body(embed=True)`。
|
||||
|
||||
### 【基础】路由函数用 `async def` 和 `def` 有什么区别?
|
||||
|
||||
**参考答案**:`async def` 在事件循环中直接执行,里面只能调用非阻塞的异步代码;普通 `def` 会被放到线程池中执行,适合调用同步阻塞库。在 `async def` 中调用阻塞函数会卡住整个事件循环,是最常见的性能问题。
|
||||
|
||||
**追问**:`def` 路由的线程池大小有限制吗?→ 有,默认由 AnyIO 的线程池限制(默认 40 个线程,以官方文档为准),大量阻塞调用会排队。
|
||||
|
||||
### 【基础】响应模型有什么作用?
|
||||
|
||||
**参考答案**:声明接口返回的数据结构:自动过滤掉未声明的字段(防止泄露内部字段),校验和序列化返回数据,并体现在 OpenAPI 文档中,前端可以据此生成类型。
|
||||
|
||||
**追问**:怎么避免把密码等字段返回给前端?→ 定义单独的输出模型(不包含敏感字段)并设置为 `response_model`,而不是直接返回数据库对象。
|
||||
|
||||
## 进阶
|
||||
|
||||
### 【进阶】`Depends` 的原理和常见用途?
|
||||
|
||||
**参考答案**:FastAPI 在处理请求时解析路由函数签名,递归解析依赖树,按顺序调用依赖函数并把返回值注入参数;同一请求内相同依赖默认只调用一次;带 `yield` 的依赖可以在请求结束后执行清理。用途:鉴权和获取当前用户 / 租户、数据库会话、分页参数、共享客户端;测试时通过 `dependency_overrides` 替换。
|
||||
|
||||
**追问**:依赖中的 `yield` 有什么用?→ `yield` 之前创建资源(数据库会话),之后清理资源;测试时可以用 `app.dependency_overrides` 替换依赖。
|
||||
|
||||
### 【进阶】中间件和依赖都能做鉴权,怎么选?
|
||||
|
||||
**参考答案**:中间件作用于所有请求,适合与业务无关的横切逻辑:请求 ID、日志、耗时、CORS。依赖按路由或路由器声明,能拿到类型化的结果(当前用户)并注入处理函数,能在测试中替换,适合鉴权、权限、租户。通常鉴权用依赖。
|
||||
|
||||
**追问**:中间件能访问路由的参数吗?→ 中间件在路由匹配之前执行,拿不到解析后的参数;需要按路由做权限控制的,用依赖。
|
||||
|
||||
### 【进阶】怎么实现大模型的流式输出?要注意什么?
|
||||
|
||||
**参考答案**:用 `StreamingResponse` 包装异步生成器,`media_type="text/event-stream"`,每个事件 `data: ...\n\n`。注意:响应头发出后不能改状态码,错误要以事件形式发送;检查客户端断开及时停止生成;内容用 JSON 编码避免换行破坏格式;经过 Nginx 时关闭缓冲;前端 POST 请求需要用 fetch 读取流。
|
||||
|
||||
**追问**:客户端中途断开,服务端还在调用大模型怎么办?→ 在生成器中检查 `await request.is_disconnected()`,或者依赖取消机制,及时停止上游调用以节省成本。
|
||||
|
||||
## 场景题
|
||||
|
||||
### 【场景题】压测发现 FastAPI 服务 QPS 很低,CPU 也不高,可能是什么原因?
|
||||
|
||||
**参考答案**:最常见的是在 `async def` 路由里调用了阻塞操作(同步数据库驱动、`requests`、`time.sleep`、同步 SDK),事件循环被阻塞。其他原因:每个请求新建 HTTP 客户端或数据库连接、连接池太小、下游(模型、数据库)本身慢、只启动了一个 worker。排查:看慢请求的耗时分布,用 py-spy 看事件循环线程在做什么。
|
||||
|
||||
**追问**:怎么验证是不是事件循环被阻塞了?→ 开启 asyncio 的调试模式(会记录执行过慢的回调),或者在压测时观察一个简单的 `/health` 接口延迟是否也升高。
|
||||
|
||||
### 【场景题】一个接口依赖大模型和向量数据库,怎么写单元测试?
|
||||
|
||||
**参考答案**:把模型客户端和检索器抽象成接口(Protocol),通过依赖注入获取;测试中用 `app.dependency_overrides` 替换为假实现,返回固定内容或模拟异常;用 `TestClient` 调用接口,断言状态码、响应格式、流式事件顺序、错误处理。真实模型的效果评估另外做(评测集),不放在单元测试里。
|
||||
|
||||
**追问**:测试流式接口要注意什么?→ 用 `TestClient` 的 `stream` 方法或 httpx 异步客户端逐块读取,断言事件格式和结束标记,而不只是最终文本。
|
||||
|
||||
### 【场景题】给多个业务系统提供 AI 能力,怎么防止某个系统把额度用光?
|
||||
|
||||
**参考答案**:每个调用方一个 API Key,鉴权依赖解析出租户;在服务端按租户做限流(Redis 滑动窗口 / 令牌桶)和配额(按日 / 月的 token 数);记录每次调用的 token 用量写入数据库;超过配额返回 429 并给出提示;对异常增长告警。Nginx 层可以再加一层按 IP 的粗粒度限流。
|
||||
|
||||
**追问**:额度用完时返回什么?→ 返回 429 和明确的错误码,带上 `Retry-After` 头;同时通知对应业务方,提供额度查询接口。
|
||||
@@ -18,28 +18,40 @@ week: [4, 16]
|
||||
|
||||
**参考答案**:不可变:int、float、str、tuple、frozenset、bytes;可变:list、dict、set、自定义对象(默认)。影响:函数参数传的是对象引用,函数里修改可变对象会影响调用方;只有不可变(可哈希)的对象才能做 dict 的 key 和 set 的元素。
|
||||
|
||||
**追问**:元组里包含列表,这个元组可以作为字典的 key 吗?→ 不可以。元组本身不可变,但是包含可变元素时不可哈希。
|
||||
|
||||
### 【基础】为什么不要用可变对象作为函数默认参数?
|
||||
|
||||
**参考答案**:默认值在函数定义时只创建一次,之后每次调用共享同一个对象。`def f(xs=[])` 多次调用会在同一个列表上累加。改为默认 `None`,在函数内部创建新列表。
|
||||
|
||||
**追问**:dataclass 的可变默认值怎么写?→ 用 `field(default_factory=list)`,直接写 `= []` 会报错。
|
||||
|
||||
### 【基础】`is` 和 `==` 的区别?
|
||||
|
||||
**参考答案**:`==` 比较值是否相等(调用 `__eq__`),`is` 比较是否是同一个对象(身份)。判断 `None` 用 `is None`;比较值永远用 `==`,不要依赖小整数缓存之类的实现细节。
|
||||
|
||||
**追问**:为什么小整数 `a is b` 有时候为 True?→ CPython 缓存了 -5 到 256 的小整数和部分字符串,这是实现细节,不能依赖;比较值一律用 `==`。
|
||||
|
||||
### 【基础】浅拷贝和深拷贝的区别?
|
||||
|
||||
**参考答案**:浅拷贝(`list(x)`、`x.copy()`、`copy.copy`)只复制最外层容器,内部元素仍是同一引用;深拷贝(`copy.deepcopy`)递归复制所有层级。嵌套结构修改内层元素时,浅拷贝会影响原对象。
|
||||
|
||||
**追问**:`copy.deepcopy` 有什么代价?→ 递归复制所有对象,大数据结构时很慢、占内存;很多情况下用不可变数据或只复制需要修改的部分更好。
|
||||
|
||||
### 【基础】列表推导式和生成器表达式有什么区别?
|
||||
|
||||
**参考答案**:`[x for x in xs]` 立即生成完整列表,占用内存;`(x for x in xs)` 生成器表达式按需逐个产生,内存占用恒定,但只能遍历一次。传给 `sum`、`max` 等函数时用生成器表达式即可。
|
||||
|
||||
**追问**:生成器只能遍历一次,怎么办?→ 需要多次遍历时转成列表,或者重新创建生成器;注意不要在调试时提前把它消耗掉。
|
||||
|
||||
## 进阶
|
||||
|
||||
### 【进阶】什么是 GIL?对 AI 应用开发有什么影响?
|
||||
|
||||
**参考答案**:GIL(全局解释器锁)使 CPython 同一时刻只有一个线程执行 Python 字节码,所以多线程无法利用多核做 CPU 密集计算;但 IO 等待时会释放 GIL,所以多线程 / asyncio 对 IO 密集任务有效。AI 应用大多是 IO 密集(调用模型 API、查数据库、检索),用 asyncio 很合适;CPU 密集(本地向量计算、文本解析)用多进程,或者依赖 numpy 等在 C 层释放 GIL 的库。Python 3.13 起提供了实验性的无 GIL 构建,以官方文档为准。
|
||||
|
||||
**追问**:Python 3.13 的无 GIL 版本是怎么回事?→ 3.13 开始提供实验性的自由线程(free-threaded)构建,可以关闭 GIL,3.14 起成为官方支持的可选构建;生态兼容还在推进中,生产使用以官方文档和依赖库的支持情况为准。
|
||||
|
||||
### 【进阶】装饰器是什么?写一个统计耗时的装饰器。
|
||||
|
||||
**参考答案**:装饰器是接收函数并返回新函数的函数,用来在不修改原函数的情况下增加功能(日志、重试、缓存、鉴权)。
|
||||
@@ -74,20 +86,30 @@ work(1_000_000)
|
||||
|
||||
**参考答案**:内部数据结构用 dataclass(轻量、无运行时校验);外部输入(接口请求体、配置、大模型返回的 JSON)用 pydantic,它会在运行时校验和转换类型并给出清晰的错误;只是给已有的字典加类型提示(例如 JSON 数据直接传来传去)用 TypedDict。
|
||||
|
||||
**追问**:pydantic 的性能怎么样?→ v2 的核心用 Rust 实现,校验很快;但在内部的热点循环中,dataclass 或普通对象开销更小。边界用 pydantic,内部用 dataclass。
|
||||
|
||||
### 【进阶】上下文管理器的原理是什么?
|
||||
|
||||
**参考答案**:实现了 `__enter__` 和 `__exit__` 方法的对象。`with` 进入时调用 `__enter__`,离开代码块(无论正常还是异常)时调用 `__exit__`,保证资源释放;`__exit__` 返回 True 会吞掉异常。也可以用 `contextlib.contextmanager` 装饰生成器函数实现,异步版本是 `async with` 和 `__aenter__` / `__aexit__`。
|
||||
|
||||
**追问**:`contextlib.contextmanager` 中,如果 `with` 块内抛出异常会怎样?→ 异常会在 `yield` 处被抛出,用 `try/finally` 包住 `yield` 才能保证清理代码执行。
|
||||
|
||||
## 场景题
|
||||
|
||||
### 【场景题】要处理一个 10GB 的日志文件,统计每个接口的请求数,怎么写?
|
||||
|
||||
**参考答案**:不能一次读入内存。用 `with open(...)` 逐行迭代(文件对象本身就是惰性的),解析出接口路径后用 `Counter` 计数;正则预编译;如果需要更快,可以按字节范围切分文件用多进程并行统计后合并。输出前 N 个用 `most_common`。
|
||||
|
||||
**追问**:统计结果也很大,放不进内存怎么办?→ 分片处理(按接口哈希写到多个中间文件再分别统计),或者导入数据库 / 使用 DuckDB 这类工具处理。
|
||||
|
||||
### 【场景题】一段 Python 代码在本机能跑,在服务器上报 `ModuleNotFoundError`,怎么排查?
|
||||
|
||||
**参考答案**:确认服务器上是否激活了正确的虚拟环境(`which python`、`python -c "import sys; print(sys.executable)"`);依赖是否按锁文件安装;运行目录和方式是否不同(直接运行子目录的文件 vs 从根目录 `python -m`);是否有本地文件和包同名遮蔽了真正的包;Python 版本是否一致。
|
||||
|
||||
**追问**:怎么保证服务器和本机的依赖一致?→ 使用锁文件(`uv.lock` / `requirements.txt` 固定版本),部署时用锁文件安装,最好用 Docker 镜像固定运行环境。
|
||||
|
||||
### 【场景题】你从前端转 Python,怎么保证写出来的 Python 代码质量?
|
||||
|
||||
**参考答案**:用 ruff 做 lint 和格式化,用 mypy / pyright 做类型检查,pytest 写测试并在 CI 中运行;遵循 PEP 8 和项目约定;数据边界用 pydantic 校验;对照 JS 的经验注意 Python 特有的坑(可变默认参数、浮点金额、导入路径)。可以举一个自己写过的、带测试的小工具为例。
|
||||
|
||||
**追问**:AI 帮你写的 Python 代码怎么验证?→ 运行测试和类型检查(pytest、mypy / pyright、ruff),阅读并理解每一行,补充边界情况的测试,不理解的代码不提交。
|
||||
Reference in new issue
Block a user