第二章 Python基础高频面试题

本章聚焦 Python 基础面试中 最高频的三大核心问题:可变与不可变对象、装饰器、GIL。三个问题分别覆盖 内存模型、函数式编程、并发机制 三大底层能力,是大厂技术面首轮必考题。理解背后考察逻辑与回答框架,能帮助你在基础面上拉开差距。

🎯 公司侧重点速览
字节 / 腾讯:更关注装饰器与闭包在工程中的实际应用,常结合框架源码提问;
阿里 / 美团:更关注 GIL 对性能的影响及多进程 / 协程的选型方案;
百度 / 京东:更关注可变 / 不可变对象与内存管理,常结合函数传参与默认参数陷阱。

📊 高频考点总览表

考点 核心考察点 公司偏好 难度 频率
可变 vs 不可变对象 内存模型、参数传递、哈希要求 百度、京东 ⭐⭐ 极高
装饰器 闭包、高阶函数、AOP 思想 字节、腾讯 ⭐⭐⭐ 高
GIL CPython 架构、并发模型、性能分析 阿里、美团 ⭐⭐⭐⭐ 高
定义概念 明确术语边界 底层原理 内存 / 机制分析 代码示例 简洁验证代码 应用场景 实际开发影响 常见陷阱 避坑要点总结 结构化回答路径:从定义到底层,再到代码和应用,最后总结陷阱

📇 面试问答卡

问题1:可变对象 vs 不可变对象

🎯 考察点 Python 内存模型、对象身份与值的区分、函数传参机制
📐 回答框架
  1. 定义:不可变对象创建后内容不可修改,可变对象可以原地修改。
  2. 分类:不可变(int, str, tuple, frozenset),可变(list, dict, set, bytearray)。
  3. 内存机制:不可变对象修改时创建新对象(id 变化),可变对象修改时 id 不变。
  4. 实际影响:函数参数传递、默认参数陷阱、哈希要求(可变对象不可哈希)。
💬 示例回答

不可变对象(int、str、tuple)创建后内容不可修改,对变量重新赋值本质是创建新对象,id 发生变化。可变对象(list、dict、set)可以原地修改,id 保持不变。这一区别直接影响函数参数传递方式、默认参数设计以及能否作为字典键。

❓ 可能追问
  • 函数参数传递是传值还是传引用?—— 本质是“传对象引用”,对象可变性决定行为。
  • 为什么不能用可变对象作为函数默认参数?—— 默认参数在函数定义时创建,多次调用共享同一对象。
  • 如何实现自定义不可变类?—— 通过 __slots__ 或重写 __setattr__ 阻止属性修改。
⚠️ 避坑提醒
🔸 混淆“变量重新赋值”与“对象内容修改”是最高频错误,面试中务必用 id() 辅助说明。
🔸 注意 tuple 中嵌套 mutable 对象时,嵌套对象内容可修改,tuple 本身仍不可变。
🔸 默认参数避免使用 [] 或 {},应使用 None 并在函数内创建。

问题2:装饰器

🎯 考察点 闭包与高阶函数理解、语法糖机制、手写代码能力
📐 回答框架
  1. 定义:装饰器是接收函数并返回增强函数的高阶函数。
  2. 原理:@decorator 等价于 func = decorator(func)。
  3. 闭包实现:内层函数捕获外层变量(原函数),形成闭包。
  4. 保留元信息:使用 functools.wraps 复制原函数名称和文档字符串。
  5. 应用场景:日志、计时、权限校验、缓存。
💬 示例回答

装饰器本质是闭包的应用。@timer 等价于 func = timer(func),timer 接收原函数,在 wrapper 中添加计时逻辑后调用原函数。实现时需用 functools.wraps 保留原函数元信息,避免函数名和文档字符串丢失。

❓ 可能追问
  • 多个装饰器的执行顺序?—— 从下到上装饰(靠近函数的先装饰),从上到下执行。
  • 如何实现带参数的装饰器?—— 再包装一层函数接收参数,返回真正的装饰器。
  • 类装饰器如何实现?—— 实现 __call__ 方法,使实例可调用。
⚠️ 避坑提醒
🔸 务必使用 @functools.wraps,否则原函数元信息丢失会导致调试困难。
🔸 多个装饰器顺序:装饰时自底向上,执行时自顶向下,与控制流相反。
🔸 带参数装饰器需要三层嵌套,勿与普通装饰器混淆结构。

问题3:GIL(全局解释器锁)

🎯 考察点 CPython 架构理解、并发编程实践经验、方案选型能力
📐 回答框架
  1. 定义:GIL 是 CPython 解释器的全局锁,保证同一时刻只有一个线程执行字节码。
  2. 设计原因:简化 CPython 内存管理(引用计数),避免多线程竞争。
  3. 影响分析:CPU 密集型任务多线程无法利用多核;IO 密集型任务因 GIL 在 IO 时释放,影响较小。
  4. 解决方案:多进程(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)开销,小规模任务可能得不偿失。
面试官追问路径图 📌 可变/不可变 函数传参机制 内存地址变化 默认参数陷阱 📌 装饰器 闭包实现原理 @wraps 作用 装饰器工厂模式 📌 GIL 释放与获取时机 多进程 vs 多线程 CPU / IO 任务选型 追问路径揭示面试官的深度:从基础概念 → 底层机制 → 工程实践 → 方案选型 💡 准备时按此路径逐层深入,面试时主动展示下一层认知
📌 复习建议
  • 每个问题先按“回答框架”的五步法组织语言,确保逻辑完整。
  • 示例回答控制在 60-100 字,面试时在此基础上展开 1-2 分钟。
  • 追问路径图展示了面试官的深度探测方向,准备时逐层模拟练习。
  • 结合公司侧重点表格,针对目标公司强化相关考点的代码实践。