简介:这份《Python 八股文.pdf》面向准备技术面试的 Python 学习者与求职者,系统梳理语言核心概念与高频考点,帮助读者在有限时间内建立完整的知识框架。内容覆盖内置电池、生成器与迭代器、列表与元组、字典实现、GIL 全局解释器锁、Python 2 与 3 差异、可变与不可变对象、闭包、装饰器及元类等主题,并配有代码示例与原理说明。资源包内含 1 个 PDF 文件,大小约 304KB,轻量便携,适合在电脑或移动设备上随时翻阅复习。目前已有 13672 人学习下载,热度较高,说明其内容对面试突击与知识查漏补缺具有实际参考价值。读者可借此快速回顾生成器 yield 机制、dict 哈希查找 O(1) 原理、GIL 对多线程的影响以及装饰器在不修改源码前提下扩展函数功能等要点,适合作为面试前的速查手册与日常巩固材料。
1. 从一道“送分题”翻车说起:Python 八股文到底考什么
“生成器和迭代器有什么区别?”——这道题在面试里出现频率高到离谱,但能完整答对的人不到三成。多数人背了“生成器是迭代器,迭代器不一定是生成器”,再往下问“那 list 为什么不是迭代器”“__iter__和__next__分别解决什么问题”,就开始卡壳。Python 八股文的考点分布其实很集中:可变与不可变类型、迭代协议、GIL 与多线程、深浅拷贝、装饰器与闭包、元类、Py2/Py3 差异。分值上,迭代协议和 GIL 几乎每场必问,深浅拷贝和装饰器是区分候选人是“背过”还是“用过”的分水岭。这份笔记适合正在准备后端、数据、测试岗面试的人,也适合想系统梳理 Python 底层机制的人。下面按考点模块拆开讲,每道题都给答题思路和易错点。
2. 高频考点:迭代协议与生成器的底层逻辑
2.1 可迭代对象、迭代器、生成器的三层关系
面试官问“list 和迭代器的区别”,真正想听的是你对迭代协议的理解。可迭代对象(Iterable)必须实现__iter__(),调用后返回一个迭代器。迭代器(Iterator)必须同时实现__iter__()和__next__(),__next__()每次返回一个值,耗尽后抛StopIteration。生成器(Generator)是迭代器的一种,但迭代器不一定是生成器——比如iter([1,2,3])返回的是 list_iterator,不是生成器。
from collections.abc import Iterable, Iterator lst = [1, 2, 3] print(isinstance(lst, Iterable)) # True,list 可迭代 print(isinstance(lst, Iterator)) # False,list 本身不是迭代器 it = iter(lst) # 通过 iter() 拿到迭代器 print(isinstance(it, Iterator)) # True print(next(it)) # 1 print(next(it)) # 2这段代码的关键在iter(lst)这一步:它调用了 list 的__iter__(),返回一个 list_iterator 对象。list 本身没有__next__(),所以不能直接next(lst)。面试时如果只答“list 可迭代但不是迭代器”,面试官会追问“那 for 循环怎么工作的”——for 循环先调iter()拿到迭代器,再反复调next()直到捕获StopIteration。把这条链路说清楚,这题才算过关。
2.2 生成器的两种创建方式与内存优势
生成器有两种产生方式:列表生成式加括号g1 = (x for x in range(10)),或者在函数里用yield。yield的核心是“暂停并保留状态”:函数执行到yield时返回值并挂起,下次next()从挂起点继续。
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b # 暂停在这里,返回 b a, b = b, a + b # 下次 next() 从这里继续 n = n + 1 return 'done' g = fib(8) for val in g: print(val) # 1 1 2 3 5 8 13 21面试官最想听的是“为什么生成器省内存”:列表会一次性把所有元素算出来放进内存,生成器只在每次next()时计算当前值,用完即弃。处理大文件逐行读取、无限序列、流式数据时,生成器是标准答案。注意return 'done'的值不会通过next()返回,而是包在StopIteration异常里,这是很多人翻车的细节。
2.3 真题演练:手写一个生成器实现斐波那契
面试题常要求“不用列表,手写斐波那契生成器”。答题思路:定义函数,用yield返回当前值,用变量交换更新状态。上面fib函数就是标准写法。如果面试官加一句“限制最大长度”,就在while里加n < max条件。如果问“生成器能不能反向遍历”,答案是默认不能,因为生成器是单向的,需要先转成列表或自己实现缓存。
提示:面试时被问到“生成器和迭代器哪个更省内存”,不要只答“生成器”,要补一句“迭代器本身不承诺省内存,比如 list_iterator 背后还是整个列表,生成器才是惰性计算”。
3. 高频考点:可变类型、深浅拷贝与参数传递
3.1 可变与不可变对象的判定标准
可变对象:list、dict、set。不可变对象:bool、int、float、tuple、str。判定标准不是“能不能改”,而是“改的时候是不是原地修改”。对不可变对象做“修改”,Python 会创建新对象;对可变对象做修改,内存地址不变。
a = [1, 2, 3] print(id(a)) # 例如 140234... a.append(4) print(id(a)) # 地址不变,原地修改 b = "hello" print(id(b)) b = b + " world" # 创建了新字符串 print(id(b)) # 地址变了面试官问“tuple 里放 list 能不能改”,答案是能改 list 里的元素,但不能改 tuple 指向的 list 引用。这题考的是“不可变”的边界:tuple 的不可变是指它持有的引用不可变,不是引用指向的对象不可变。
3.2 赋值、浅拷贝、深拷贝的差异与代码验证
赋值(=)只是创建新引用,两个变量指向同一个对象。浅拷贝(copy.copy)创建新对象,但内部元素还是原对象的引用。深拷贝(copy.deepcopy)递归复制所有层级,完全独立。
import copy origin = [[1, 2], [3, 4]] assign = origin shallow = copy.copy(origin) deep = copy.deepcopy(origin) origin[0][0] = 99 print(assign) # [[99, 2], [3, 4]] 跟着变 print(shallow) # [[99, 2], [3, 4]] 内层还是同一个引用 print(deep) # [[1, 2], [3, 4]] 完全独立这段代码是面试白板题的高频版本。答题时先画引用关系:assign和origin是同一个盒子;shallow是新盒子但里面装的是原盒子的内层引用;deep是新盒子加新内层。面试官追问“什么时候用深拷贝”,答“需要完全隔离修改、或者对象里有嵌套可变结构且不想互相影响时”。
3.3 真题演练:函数默认参数的坑
def add_item(item, lst=[]): lst.append(item) return lst print(add_item(1)) # [1] print(add_item(2)) # [1, 2] 不是 [2]默认参数在函数定义时只求值一次,所以lst=[]是同一个列表。正确写法是lst=None,函数体内判断if lst is None: lst = []。这题考的是“可变默认参数”这个经典失分点,面试官想看你有没有踩过这个坑。
注意:浅拷贝只复制最外层,内层嵌套的可变对象仍然共享。深拷贝遇到循环引用会递归处理,但性能开销大,不是所有场景都该用。
4. 高频考点:GIL、多线程与多进程的选择
4.1 GIL 是什么、为什么存在
GIL(Global Interpreter Lock)是 CPython 解释器层面的全局锁,确保同一时刻只有一个线程执行 Python 字节码。它不是 Python 语言特性,而是 CPython 实现多线程时的同步机制。设计初衷是单核时代高效利用 CPU,并保护解释器内部数据结构(如引用计数)的线程安全。
面试官问“GIL 加在哪一层”,标准答案是“加在解释器层,不是语言层,也不是操作系统层”。因为 Python 调用的原生线程由 C 语言控制,Python 虚拟机无法直接管理,只能在解释器这一层加锁。
4.2 多线程、多进程、IO 密集与 CPU 密集的对应关系
GIL 导致多线程无法利用多核做并行计算,但 IO 操作时会释放 GIL,所以 IO 密集型任务用多线程仍然有效。CPU 密集型任务要用多进程(multiprocessing)绕过 GIL。
| 任务类型 | 推荐方案 | 原因 |
|---|---|---|
| IO 密集(网络、文件) | 多线程 / asyncio | IO 等待时释放 GIL,线程切换开销小 |
| CPU 密集(计算、图像) | 多进程 | 每个进程独立解释器,各自持有 GIL |
| 混合型 | 多进程 + 多线程 | 进程间并行计算,进程内并发 IO |
import multiprocessing def cpu_task(n): return sum(i * i for i in range(n)) if __name__ == "__main__": with multiprocessing.Pool(4) as pool: results = pool.map(cpu_task, [10**6] * 4) print(results)这段代码用进程池把四个计算任务分到四个进程,每个进程有独立的 GIL,真正并行。注意if __name__ == "__main__"在 Windows 和 macOS 上是必须的,否则子进程会重复导入主模块导致递归创建进程。
4.3 真题演练:Python 多线程能不能提速
面试题:“我有一个下载 100 个网页的任务,用多线程能提速吗?”答题思路:能,因为网络 IO 会释放 GIL,线程在等待响应时其他线程可以继续发起请求。但如果任务是“对 100 个大数组做排序”,多线程不能提速,要用多进程。面试官最想听的是“区分 IO 密集和 CPU 密集”这个判断标准,而不是背“GIL 让多线程变鸡肋”。
提示:Python 3.x 用计时器替代了 Python 2.x 的 ticks 计数器,达到时间阈值后释放 GIL。但无论 2.x 还是 3.x,GIL 都没有被移除,多核并行仍然要靠多进程。
5. 易错点:五道高频翻车题与正确答法
5.1 错把“可迭代”当“迭代器”
错在哪:回答“list 是迭代器”。为什么错:list 只有__iter__(),没有__next__(),不满足迭代器协议。怎么答对:先给定义——可迭代对象实现__iter__,迭代器同时实现__iter__和__next__;再举例子——list 是可迭代对象,iter(list)返回的才是迭代器;最后补一句“生成器是迭代器的一种”。
5.2 混淆浅拷贝和深拷贝的适用场景
错在哪:认为copy.copy能完全隔离修改。为什么错:浅拷贝只复制最外层容器,内层嵌套对象仍是共享引用。怎么答对:画两层结构——外层新对象,内层旧引用;深拷贝递归复制所有层级。面试时主动说“如果对象只有一层,浅拷贝和深拷贝效果一样;有嵌套时必须用 deepcopy”。
5.3 默认参数用可变对象
错在哪:def f(lst=[])然后往里 append。为什么错:默认参数在函数定义时求值一次,之后所有调用共享同一个列表。怎么答对:默认值用None,函数体内判断并初始化;同时说明“这是 Python 的常见陷阱,不是 bug,是设计如此”。
5.4 认为多线程一定能利用多核
错在哪:说“Python 多线程可以并行计算”。为什么错:GIL 保证同一时刻只有一个线程执行字节码,多核 CPU 上多线程计算任务反而因切换开销变慢。怎么答对:先区分 IO 密集和 CPU 密集,IO 密集用多线程,CPU 密集用多进程;再补一句“GIL 是 CPython 的实现细节,不是 Python 语言规范”。
5.5 装饰器忘记保留原函数元信息
错在哪:写装饰器直接return inner,导致原函数的__name__、__doc__丢失。为什么错:inner覆盖了原函数,元信息没有传递。怎么答对:用functools.wraps装饰inner,保留原函数名称和文档字符串。
import functools def log(func): @functools.wraps(func) # 保留 func 的元信息 def inner(*args, **kwargs): print(f"call {func.__name__}") return func(*args, **kwargs) return inner @log def greet(name): """打招呼""" return f"hello {name}" print(greet.__name__) # greet,不是 inner面试时如果被问到“装饰器怎么写”,先写闭包结构,再加functools.wraps,最后说明“装饰器本质是接收函数返回函数的闭包”。
6. 答题技巧:从八股文到面试现场的转化方法
面试官问八股文,不是要你背定义,是要看你有没有“用过”和“想过”。我自己的习惯是:每道题先给一句话结论,再给一个代码或场景例子,最后补一个易错点。比如被问“深拷贝浅拷贝”,先说“浅拷贝只复制外层,深拷贝递归复制”,再写三行代码演示,最后补“默认参数用可变对象是同类坑”。这样答,面试官会觉得你不仅背过,还踩过坑。
另一个技巧是“主动暴露边界”。被问 GIL,不要只答“多线程不能并行”,要补“IO 密集仍然有效,CPU 密集用多进程,GIL 是 CPython 实现不是语言规范”。被问生成器,不要只答“省内存”,要补“生成器是迭代器,但迭代器不一定是生成器,return值包在StopIteration里”。面试官最想听的是你知不知道“什么时候不适用”。
押题方向上,迭代协议、GIL、深浅拷贝、装饰器、可变默认参数这五个点覆盖了八成以上的 Python 八股文面试。每个点准备一个定义、一个代码例子、一个易错点,就能应对大部分追问。如果面试官问“Py2 和 Py3 的区别”,重点说print函数化、默认编码 UTF-8、range返回迭代器、除法返回 float、str是 unicode。这些差异背后是“Python 3 更一致、更少隐式行为”的设计取向。
从那以后我每次准备面试,都强制走一遍“定义 + 代码 + 易错点”的三段式,把每个考点压缩成三句话。希望帮到你。
本文还有配套的精品资源,点击获取