1. Python中列表、元组的区别?深拷贝和浅拷贝的区别?
答:
列表 vs 元组
| | |
|---|
| 可变性 | | |
| 语法 | [1, 2, 3] | (1, 2, 3) |
| 性能 | | |
| 哈希 | | 可哈希(元素也需可哈希),可以作为dict的key |
| 使用场景 | | |
深拷贝 vs 浅拷贝
| | |
|---|
| 方式 | copy.copy() | copy.deepcopy() |
| 行为 | | |
| 影响 | | |
import copya = [[1, 2], [3, 4]]b = copy.copy(a) # 浅拷贝:b[0] is a[0] → Truec = copy.deepcopy(a) # 深拷贝:c[0] is a[0] → Falsea[0][0] = 99print(b) # [[99, 2], [3, 4]] ← 浅拷贝受影响print(c) # [[1, 2], [3, 4]] ← 深拷贝不受影响
延伸问题:
Q: Python中哪些操作会产生浅拷贝?A: copy.copy()、列表切片[:]、list()构造函数、dict.copy()、{k: v for k, v in d.items()}等。
Q: 元组真的完全不可变吗?A: 元组本身不可变(不能增删元素),但如果元组包含可变对象(如列表),该可变对象的内容可以修改:t = (1, [2, 3]),t[1].append(4) 是合法的。
2. 迭代器和生成器的区别?
答:
| | |
|---|
| 定义 | 实现了__iter__()和__next__()的对象 | |
| 创建方式 | | 函数中使用yield或(x for x in range(n)) |
| 内存 | | |
| 代码量 | | |
# 迭代器classMyIter:def__init__(self, n): self.n, self.i = n, 0def__iter__(self):return selfdef__next__(self):if self.i >= self.n: raise StopIteration self.i += 1; return self.i# 生成器(等价实现)defmy_gen(n):for i in range(1, n+1):yield i
核心区别: 生成器是迭代器的"语法糖",用更简洁的代码实现相同功能,且自动支持惰性求值(按需生成数据,不一次性加载到内存)。
延伸问题:
Q: yield和return有什么区别?A: return返回值并结束函数;yield返回值但暂停函数,下次调用next()时从暂停处继续执行。
Q: 生成器只能遍历一次吗?为什么?A: 是的。生成器是惰性的,遍历时消费元素,遍历完后抛出StopIteration。如果需要重复遍历,需要重新创建生成器。
3. 有序数组插入新数的时间复杂度?如何判断链表有环?
答:
有序数组插入
- 原因:需要先用二分查找O(log n)找到插入位置,但插入时需要将该位置及之后的元素全部后移一位,最坏情况移动n个元素,所以总体是O(n)。
判断链表有环:快慢指针法(Floyd判圈算法)
defhas_cycle(head): slow = fast = headwhile fast and fast.next: slow = slow.next fast = fast.next.nextif slow == fast:returnTruereturnFalse
- 原理: 慢指针每次走1步,快指针每次走2步。如果有环,快指针一定会追上慢指针(在环内相遇);如果无环,快指针先到达None。
延伸问题:
Q: 如何找到环的入口节点?A: 快慢指针相遇后,将其中一个指针重置到head,两个指针都每次走1步,再次相遇的位置就是环的入口。数学证明:设头到入口距离a,入口到相遇点b,环长c,则2(a+b) = a+b+nc,得a = nc-b = (n-1)c + (c-b),即从head和相遇点同时走会在入口相遇。
Q: 还有其他方法判断链表有环吗?A: 哈希表法:遍历链表,将每个节点存入set,如果遇到已存在的节点说明有环。时间O(n),空间O(n)。
4. 算法题:两数之和、二分查找
答:
两数之和(Two Sum)
deftwo_sum(nums, target): seen = {}for i, num in enumerate(nums): complement = target - numif complement in seen:return [seen[complement], i] seen[num] = ireturn []
- 思路: 哈希表一次遍历,边存边查。时间O(n),空间O(n)。
二分查找(Binary Search)
defbinary_search(nums, target): left, right = 0, len(nums) - 1while left <= right: mid = left + (right - left) // 2if nums[mid] == target:return midelif nums[mid] < target: left = mid + 1else: right = mid - 1return-1
- 思路: 每次将搜索区间缩小一半。时间O(log n),空间O(1)。
- 注意:
mid = left + (right - left) // 2 防止 (left + right) 整数溢出。
延伸问题:
Q: 两数之和如果有重复元素怎么处理?A: 返回所有符合条件的索引对。可以用defaultdict(list)存储每个值的所有索引。
Q: 二分查找的边界条件容易出错的地方?A: left <= right 还是 left < right 取决于区间定义(闭区间 vs 左闭右开)。循环不变量要一致:如果用闭区间[left, right],则left <= right;如果用左闭右开[left, right),则left < right。
5. 装饰器的作用是什么?
答: 装饰器(Decorator)是Python的语法糖,用于在不修改原函数代码的情况下,为函数/类添加额外功能。本质是一个高阶函数(接收函数作为参数,返回新函数)。
import timedeftimer(func):defwrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f"{func.__name__} 耗时: {time.time()-start:.3f}s")return resultreturn wrapper@timerdefslow_function(): time.sleep(1)
常见用途:
延伸问题:
Q: 如何保留被装饰函数的元信息(如__name__)?A: 在wrapper函数上加@functools.wraps(func),它会将原函数的__name__、__doc__等属性复制到wrapper上。
Q: 装饰器可以带参数吗?怎么实现?A: 可以,需要三层嵌套函数:外层接收装饰器参数,中层接收被装饰函数,内层是实际执行逻辑。例如@retry(max_attempts=3)。
6. Python有哪些常用的库?
答: 按领域分类:
| |
|---|
| 数据处理 | NumPy(数值计算)、Pandas(数据分析)、Polars(高性能DataFrame) |
| 可视化 | Matplotlib、Seaborn、Plotly |
| 机器学习 | Scikit-learn、XGBoost、LightGBM |
| 深度学习 | PyTorch、TensorFlow、Transformers(HuggingFace) |
| NLP | |
| Web框架 | |
| 爬虫 | Requests、Scrapy、BeautifulSoup、Selenium |
| 数据库 | SQLAlchemy、PyMySQL、Pymongo、pymilvus |
| 异步 | |
| 工具 | os、json、re、logging、argparse |
延伸问题:
Q: NumPy的ndarray和Python list有什么区别?A: ndarray是同质连续内存数组,支持向量化运算(无需循环),底层C实现,速度比list快10-100倍;list是Python对象数组,元素类型可以不同,支持动态增删。
7. Flask和FastAPI的区别?有没有更深入的了解?
答:
| | |
|---|
| 发布年份 | | |
| 性能 | | |
| 类型检查 | | |
| API文档 | | |
| 异步支持 | | |
| 依赖注入 | | |
| 适合场景 | | |
FastAPI核心优势:
- 自动文档: 访问
/docs即可看到Swagger UI
from fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI()classItem(BaseModel): name: str price: float@app.post("/items/")asyncdefcreate_item(item: Item):# 自动校验请求体return {"name": item.name, "total": item.price * 1.1}
延伸问题:
Q: FastAPI的依赖注入(Depends)是什么?有什么好处?A: Depends是FastAPI的依赖注入系统,可以将通用逻辑(如数据库连接、认证、参数解析)抽取为独立的依赖函数,由框架自动调用。好处:代码复用、解耦、便于测试。
Q: WSGI和ASGI有什么区别?A: WSGI(Web Server Gateway Interface)是同步标准,一次处理一个请求;ASGI(Asynchronous Server Gateway Interface)是异步标准,支持WebSocket、HTTP/2等,能并发处理多个请求。
8. 用Python代码怎么实现流式输出?
答:
# 方式一:SSE(Server-Sent Events)— 最常用from fastapi import FastAPIfrom fastapi.responses import StreamingResponseimport asyncioapp = FastAPI()asyncdefgenerate_stream():for chunk in ["Hello", " World", "!"]:yieldf"data: {chunk}\n\n"await asyncio.sleep(0.5)@app.get("/stream")asyncdefstream():return StreamingResponse(generate_stream(), media_type="text/event-stream")# 方式二:调用大模型的流式APIfrom openai import OpenAIclient = OpenAI()stream = client.chat.completions.create( model="gpt-4", messages=[{"role": "user", "content": "你好"}], stream=True)for chunk in stream:if chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True)
流式输出的核心原理: 服务端不等全部数据生成完再返回,而是每生成一部分就通过HTTP长连接推送一部分给客户端。客户端逐块接收并渲染。
延伸问题:
Q: 流式输出和普通HTTP响应有什么区别?A: 普通HTTP响应是"请求-等待-完整返回";流式输出是"请求-逐块返回",客户端无需等待全部数据生成,首字延迟(TTFT)更低,用户体验更好。
9. 流式输出怎么判断是否全部结束?
答:
# SSE协议中,通过特殊标记判断结束asyncdefgenerate():for chunk in data:yieldf"data: {chunk}\n\n"yield"data: [DONE]\n\n"# 结束标记# 客户端判断for chunk in stream:if chunk.strip() == "data: [DONE]": print("流式输出结束")break# 处理正常数据...# OpenAI风格:检查 finish_reasonfor chunk in stream:if chunk.choices[0].finish_reason == "stop": print("生成结束")break
常见判断方式:
- finish_reason字段: 模型返回的元数据中标记结束原因(
stop/length)
延伸问题:
Q: 流式输出中如果网络断开怎么办?A: 客户端检测到连接断开后,可以实现重连机制(携带上次的position参数请求续传),服务端需要支持从指定位置继续生成。也可以使用断点续传的SSE方案。
10. MySQL数据库如果有十万条数据,性能如何优化?
答:
1. 索引优化
-- 为常用查询字段创建索引CREATEINDEX idx_name ONusers(name);-- 复合索引(遵循最左前缀原则)CREATEINDEX idx_name_age ONusers(name, age);-- 覆盖索引:查询字段都在索引中,避免回表SELECTname, age FROMusersWHEREname = '张三';
2. 查询优化
- 避免在WHERE条件中对索引列使用函数(如
WHERE YEAR(create_time) = 2024) - 用
EXPLAIN分析执行计划,关注type、key、rows、Extra字段
3. 分页优化
-- 慢:OFFSET越大越慢SELECT * FROMusersLIMIT90000, 10;-- 快:基于游标的分页SELECT * FROMusersWHEREid > 90000LIMIT10;
4. 架构优化
延伸问题:
Q: MySQL索引的底层数据结构是什么?为什么用B+树而不是B树?A: InnoDB使用B+树。B+树的优势:1) 非叶子节点只存key不存data,单个节点能存更多key,树更矮,IO次数更少;2) 叶子节点用链表连接,范围查询高效;3) 所有数据都在叶子节点,查询效率稳定。
11. Redis存放哪些数据?
答: 在RAG/AI系统中,Redis常用于存放:
| | |
|---|
| 会话缓存 | | |
| 热点查询缓存 | | |
| 分布式锁 | | |
| 限流计数器 | | |
| 用户Session | | |
| 任务队列 | | |
| 排行榜 | | |
| 布隆过滤器 | | |
import redisr = redis.Redis(host='localhost', port=6379, db=0)# 缘存查询结果(String)r.setex("query:你好", 3600, json.dumps({"answer": "你好啊"}))# 存储对话历史(List)r.rpush("chat:user123", "用户: 你好", "AI: 你好啊")r.ltrim("chat:user123", -20, -1) # 只保留最近20条
延伸问题:
Q: Redis和Memcached有什么区别?A: Redis支持更丰富的数据结构(String/List/Hash/Set/ZSet)、支持持久化(RDB/AOF)、支持发布订阅、支持Lua脚本;Memcached只支持简单的key-value,多线程架构,在纯缓存场景下性能略优。
12. Redis是怎么进行持久化缓存的?
答: Redis提供两种持久化方式:
RDB(Redis Database Snapshot)
- 原理: 在指定时间间隔内,将内存中的数据快照写入磁盘(
.rdb文件) - 触发方式:
SAVE(阻塞)/ BGSAVE(后台fork子进程)
AOF(Append Only File)
- 同步策略:
always(每条命令)/ everysec(每秒)/ no(由OS决定)
混合持久化(Redis 4.0+)
- 原理: AOF重写时,前半部分用RDB格式,后半部分用AOF格式
# redis.conf 配置save 900 1 # 900秒内至少1个key变化则触发RDBappendonly yes # 开启AOFappendfsync everysec # 每秒同步一次
延伸问题:
Q: Redis的内存淘汰策略有哪些?A: 8种策略:noeviction(不淘汰,写入报错)、allkeys-lru(所有key中淘汰最近最少使用)、volatile-lru(过期key中淘汰LRU)、allkeys-lfu(淘汰最不经常使用)、volatile-lfu、allkeys-random、volatile-random、volatile-ttl(淘汰TTL最短的)。生产环境常用allkeys-lru。
核心知识点分析
| | |
|---|
| | |
| | |
| | |
| | |
| | |
| | |
| Flask vs FastAPI、WSGI/ASGI | |
| | |
| | |
| | |
| | |
| | |
知识图谱总结
本模块围绕Python基础与后端工程展开,涵盖四大核心主题:
- Python语言基础(1-5): 数据类型、深浅拷贝、迭代器/生成器、装饰器——考察对Python语言本质的理解深度。
- 算法与数据结构(3-4): 时间复杂度分析、快慢指针、哈希查找、二分查找——考察编码基本功。
- Web框架与API设计(7-9): Flask/FastAPI选型、流式输出实现、SSE协议——考察后端工程能力。
- 数据库与缓存(10-12): MySQL索引优化、Redis数据结构与持久化——考察数据层设计能力。