第二章 Python基础高频面试题
本章聚焦 Python 基础面试中 最高频的三大核心问题:可变与不可变对象、装饰器、GIL。三个问题分别覆盖 内存模型、函数式编程、并发机制 三大底层能力,是大厂技术面首轮必考题。理解背后考察逻辑与回答框架,能帮助你在基础面上拉开差距。
🎯 公司侧重点速览
字节 / 腾讯:更关注装饰器与闭包在工程中的实际应用,常结合框架源码提问;
阿里 / 美团:更关注 GIL 对性能的影响及多进程 / 协程的选型方案;
百度 / 京东:更关注可变 / 不可变对象与内存管理,常结合函数传参与默认参数陷阱。
字节 / 腾讯:更关注装饰器与闭包在工程中的实际应用,常结合框架源码提问;
阿里 / 美团:更关注 GIL 对性能的影响及多进程 / 协程的选型方案;
百度 / 京东:更关注可变 / 不可变对象与内存管理,常结合函数传参与默认参数陷阱。
📊 高频考点总览表
| 考点 | 核心考察点 | 公司偏好 | 难度 | 频率 |
|---|---|---|---|---|
| 可变 vs 不可变对象 | 内存模型、参数传递、哈希要求 | 百度、京东 | ⭐⭐ | 极高 |
| 装饰器 | 闭包、高阶函数、AOP 思想 | 字节、腾讯 | ⭐⭐⭐ | 高 |
| GIL | CPython 架构、并发模型、性能分析 | 阿里、美团 | ⭐⭐⭐⭐ | 高 |
📇 面试问答卡
问题1:可变对象 vs 不可变对象
📐 回答框架
- 定义:不可变对象创建后内容不可修改,可变对象可以原地修改。
- 分类:不可变(int, str, tuple, frozenset),可变(list, dict, set, bytearray)。
- 内存机制:不可变对象修改时创建新对象(id 变化),可变对象修改时 id 不变。
- 实际影响:函数参数传递、默认参数陷阱、哈希要求(可变对象不可哈希)。
💬 示例回答
不可变对象(int、str、tuple)创建后内容不可修改,对变量重新赋值本质是创建新对象,id 发生变化。可变对象(list、dict、set)可以原地修改,id 保持不变。这一区别直接影响函数参数传递方式、默认参数设计以及能否作为字典键。
❓ 可能追问
- 函数参数传递是传值还是传引用?—— 本质是“传对象引用”,对象可变性决定行为。
- 为什么不能用可变对象作为函数默认参数?—— 默认参数在函数定义时创建,多次调用共享同一对象。
- 如何实现自定义不可变类?—— 通过
__slots__或重写__setattr__阻止属性修改。
⚠️ 避坑提醒
🔸 混淆“变量重新赋值”与“对象内容修改”是最高频错误,面试中务必用 id() 辅助说明。
🔸 注意 tuple 中嵌套 mutable 对象时,嵌套对象内容可修改,tuple 本身仍不可变。
🔸 默认参数避免使用
🔸 注意 tuple 中嵌套 mutable 对象时,嵌套对象内容可修改,tuple 本身仍不可变。
🔸 默认参数避免使用
[] 或 {},应使用 None 并在函数内创建。
问题2:装饰器
📐 回答框架
- 定义:装饰器是接收函数并返回增强函数的高阶函数。
- 原理:
@decorator等价于func = decorator(func)。 - 闭包实现:内层函数捕获外层变量(原函数),形成闭包。
- 保留元信息:使用
functools.wraps复制原函数名称和文档字符串。 - 应用场景:日志、计时、权限校验、缓存。
💬 示例回答
装饰器本质是闭包的应用。@timer 等价于 func = timer(func),timer 接收原函数,在 wrapper 中添加计时逻辑后调用原函数。实现时需用 functools.wraps 保留原函数元信息,避免函数名和文档字符串丢失。
❓ 可能追问
- 多个装饰器的执行顺序?—— 从下到上装饰(靠近函数的先装饰),从上到下执行。
- 如何实现带参数的装饰器?—— 再包装一层函数接收参数,返回真正的装饰器。
- 类装饰器如何实现?—— 实现
__call__方法,使实例可调用。
⚠️ 避坑提醒
🔸 务必使用
🔸 多个装饰器顺序:装饰时自底向上,执行时自顶向下,与控制流相反。
🔸 带参数装饰器需要三层嵌套,勿与普通装饰器混淆结构。
@functools.wraps,否则原函数元信息丢失会导致调试困难。🔸 多个装饰器顺序:装饰时自底向上,执行时自顶向下,与控制流相反。
🔸 带参数装饰器需要三层嵌套,勿与普通装饰器混淆结构。
问题3:GIL(全局解释器锁)
📐 回答框架
- 定义:GIL 是 CPython 解释器的全局锁,保证同一时刻只有一个线程执行字节码。
- 设计原因:简化 CPython 内存管理(引用计数),避免多线程竞争。
- 影响分析:CPU 密集型任务多线程无法利用多核;IO 密集型任务因 GIL 在 IO 时释放,影响较小。
- 解决方案:多进程(multiprocessing)、协程(asyncio)、C 扩展释放 GIL。
💬 示例回答
GIL 是 CPython 解释器中的互斥锁,保证同一时间只有一个线程执行 Python 字节码,源于 CPython 内存管理非线程安全。对 CPU 密集型任务,多线程反而因 GIL 竞争比单线程更慢,应使用多进程;对 IO 密集型任务,GIL 在 IO 等待时释放,多线程仍有效。
❓ 可能追问
- GIL 在什么时机释放和重新获取?—— 每执行 100 条字节码或遇到 IO 操作时释放,由操作系统调度。
- 如何测量 GIL 对程序的影响?—— 使用 profile 工具对比单线程 / 多线程 / 多进程的 CPU 耗时。
- Python 3 对 GIL 做了哪些改进?—— 引入“分代垃圾回收”与“细粒度锁”,但 GIL 并未移除。
⚠️ 避坑提醒
🔸 不简单断言“GIL 是坏的”,要说明其设计背景:简化 CPython 实现,保障 C 扩展兼容。
🔸 区分 CPU 密集型与 IO 密集型任务,不同场景下 GIL 的影响差异巨大。
🔸 多进程是常用方案,但注意进程间通信(IPC)开销,小规模任务可能得不偿失。
🔸 区分 CPU 密集型与 IO 密集型任务,不同场景下 GIL 的影响差异巨大。
🔸 多进程是常用方案,但注意进程间通信(IPC)开销,小规模任务可能得不偿失。
📌 复习建议
- 每个问题先按“回答框架”的五步法组织语言,确保逻辑完整。
- 示例回答控制在 60-100 字,面试时在此基础上展开 1-2 分钟。
- 追问路径图展示了面试官的深度探测方向,准备时逐层模拟练习。
- 结合公司侧重点表格,针对目标公司强化相关考点的代码实践。