ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python面试题55道怎么刷?拆考点、跑代码、建错题本

Python面试题55道怎么刷?拆考点、跑代码、建错题本

简介:Python 面试看似在考语法,实则考的是对对象模型、内存引用和函数式特性的底层理解。从变量作用域、is 与 == 的区别,到闭包、装饰器、生成器,再到深拷贝与浅拷贝的边界,几乎每一道高频面试题都能回溯到“对象在内存中如何创建、共享与销毁”这条主线。掌握这些原理,不仅能应对面试官的连环追问,也能在工程实践中更稳健地处理缓存、大文件分块读取、接口鉴权等真实场景。本文以一份 55 道 Python 面试题文档为例,演示如何把零散题目按考点重新分组、用 IDE 验证答案、建立错题本并扩展追问层次,让刷题从机械记忆转变成可迁移的认知体系,真正提升 Python 编程功底与面试表现。

1. 拿到这份 55 道题的 Python 面试文档,先别急着翻答案

我见过不少拿到一份「Python面试题及答案共55道.docx」就从头往下背的人,背到第 20 题时已经飘了,考场上遇到原题也能答对,但面试官多问一句“为什么”,当场卡壳。这 55 道题对初级/中级 Python 求职者、准备转 Python 开发的人,以及想给基础语法做自查的老手来说,最大价值不是“答案”,而是考点分布。它像一张考点地图,扫一遍目录就能看出哪些概念被反复提及、哪些细节容易被设计成陷阱题。别把地图当教案,也别把答案当结论。我的处理方式很简单:先把 55 道题拆成几个大主题,再逐题用 IDE 验证,最后整理成自己的错题集。靠背是没有安全感的,跑过代码的题才不怕追问。

2. 拆解 55 道题:先给文档建立考点坐标

这份文档不管按什么顺序排列,题目之间一定有隐藏逻辑。如果直接顺着 1 到 55 过,你会发现自己在一道“装饰器”和一道“闭包陷阱”之间反复切换,学到后面忘前面。更稳的做法是先不看答案,只扫描题干,把题目按 Python 的核心机制重新分组。

2.1 把题目按 Python 对象模型重新分组

我习惯把这一类的 PDF、Word 题库先切分成几大类。下面这张表是我自用的分组方式,你也可以按自己的薄弱点调整:

分组常见入口题真正考什么
基础对象与语法变量作用域、is 与 ==、可变默认参数对象创建时机、名字查找顺序
函数式特性装饰器、闭包、lambda、函数参数函数也是对象、闭包变量绑定时机
迭代与生成迭代器、生成器、yield、列表推导式惰性求值、一次性消费、执行状态保存
内存与引用深拷贝/浅拷贝、引用计数、循环引用对象在内存中的共享与边界
并发与资源GIL、多线程、多进程、协程、with 语句同步模型、阻塞、资源生命周期

分完组之后你会发现,文档里前后隔得很远的两道题,本质可能是同一个问题。比如“可变默认参数”和“列表的 append 返回 None”,看起来一个考函数、一个考方法,但它们都落在“对象是否原地修改”这条线上。把它们放在一起复习,比单独记 55 个零散点高效得多。

2.2 反推考点地图:文档顺序不等于复习顺序

文档作者排题通常会按“由易到难”,但面试官提问是按“由底层到场景”来的。我拿到这份 Python面试题及答案共55道.docx 后,会先用一小时把每道题的主题二到五个字写在题号旁边,形成一张考点地图。

举个例子,文档里如果先出现“深拷贝和浅拷贝的区别”,又出现“copy.deepcopy 对嵌套对象的表现”,我基本能判断这组题在考一个东西:可变对象在复制时,引用边界在哪。复习时就不需要每个题各背一段,而是把这一类合并成一段有推导过程的话。

这样做的另一个好处是方便查漏。如果你发现某个大组里几乎没有任何热词相关题目,不代表考不到。常见做法是补一份 Python 官方文档里的对应章节,把基础例子跑一遍,再回到题库里找类比题。地图的价值就在这里:它告诉你哪里空白,而不是让你把 55 道题背成一片模糊。

2.3 自评表和优先级:把 55 道题切成 A/B/C 三类

我给这 55 道题做了一道分拣:A 类题必须是能闭眼手写的,B 类题要能说出原理和取舍,C 类题知道名词、能查、能写 demo 就够了。这道分拣不是让偷懒,而是避免把精力平均分配。

自评表我一般用 Markdown 建,格式很固定:

题号主题首次盲答错因考点本质题干变体回看日期
1可变默认参数✗只记住了结论默认参数在定义阶段创建换 dict 还能解释吗第2天

盲答、错因、本质、变体,四列缺一不可。盲答用来标记状态;错因用来区分“没学过”和“学了但错”;本质用来合并同类题;变体是最高价值,后面面试时几乎所有追加问题都从变体来。

3. 把文档答案还原成可运行代码:四组必练题实操

这 55 道题里,凡是能改成代码的,我都不建议只在文档里看文字答案。打开 IDE,把题目变成脚本,再用 print 验证每一个中间状态。文中的“答案”只是期望结果,代码跑出来的结果才是你自己的证据。下面四组是文档里几乎必然会出现的经典主题,也是至少我反复见到的。

3.1 可变默认参数:连续调用两次,观察同一个列表对象

这类题标准问法是:定义一个默认参数为列表的函数,连续调用两次,为什么第二次结果里保留了第一次的数据?

def add_item(item, arr=[]): arr.append(item) return arr print(add_item(1)) # [1] print(add_item(2)) # [1, 2] ? 明明每次都传了空列表 print(add_item.__defaults__) # ([1, 2],)

逻辑说明:函数定义时,默认参数 arr 的列表对象只创建一次,并被保存在__defaults__中。第一次调用后,列表变成了[1];第二次调用没有重新创建空列表,所以直接在此基础上 append。第三行打印__defaults__是验证这个坑最直接的手段。

面试时不要只回答“不能这么写”,而要补一句:“默认参数在 def 执行时求值并缓存,可变对象会被后续调用共享。”这句话是把一个死结论转成对象模型的证据。把函数改成arr=None,函数内部再重新创建列表即可规避。

def add_item(item, arr=None): if arr is None: arr = [] arr.append(item) return arr print(add_item(1)) # [1] print(add_item(2)) # [2]

参数说明:item是入参,arr是共享默认值的避雷标准写法。这道题真正考的是“定义时创建”这个时机,而不是列表本身。

3.2 深拷贝与浅拷贝:用 id 验证复制边界

文档答案大概率会写“浅拷贝只拷贝一层,深拷贝递归拷贝所有层”。这句话不少人背下来了,但面对稍微嵌套的用例还是会翻车。我把代码跑出来看:

import copy original = [[1, 2], [3, 4]] shallow = copy.copy(original) deep = copy.deepcopy(original) # 浅拷贝后,外层是新列表,内层仍是同一个对象 print(id(original[0]), id(shallow[0]), id(deep[0])) shallow[0].append(99) print(original) # [[1, 2, 99], [3, 4]] print(deep) # [[1, 2], [3, 4]]

逻辑说明:copy.copy复制外层列表,但列表里的元素依然是原引用,所以shallow[0]和original[0]的 id 相同。deepcopy会把嵌套的 list 也重新创建,于是修改shallow[0]不会再动到 original。

追问常出现“浅拷贝的 list 里放一个不可变对象,append 会怎样”。对这种变体,不要背结论,直接在代码里把original[0]换成 tuple,再跑一次就清楚了。文档里很多“区别”题,本质上都能用 id 和修改验证。

3.3 装饰器:三行模板加上 functools.wraps

文档里装饰器相关题一般给一个很短的示例。新手照着抄一遍觉得懂,但面试官改个参数或问一句“原函数名还在吗”就崩了。我固化的实现模板是下面这样:

import functools def log(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f"调用 {func.__name__}") result = func(*args, **kwargs) print(f"返回 {result}") return result return wrapper @log def add(a, b): """两数相加""" return a + b print(add(2, 3)) print(add.__name__) # 有 wraps:add print(add.__doc__) # 有 wraps:两数相加

逻辑说明:log接收原函数,wrapper是新增逻辑的执行者,*args和**kwargs负责透传调用参数。functools.wraps(func)把原函数名、文档字符串等元信息复制到 wrapper 上,这一步在面试里经常被单独问。

如果没有 wraps,add.__name__会变成 wrapper。很多面试官会问“为什么加上 wraps”,答案就是保留函数元信息。这道题的边界是装饰器执行时机:@log本质上相当于执行了add = log(add),如果题目改成“装饰器接收参数”,需要再套一层外层函数,逻辑仍然是返回值替换。

3.4 生成器与迭代器:手推 next 和 send 的执行状态

生成器题的难点不在语法,而在执行状态。yield 不是 return,它在代码中做了一个暂停点。我习惯用 send 来验证这个暂停点:

def counter(): print("start") n = 0 while n < 3: got = yield n print("got:", got) n += 1 gen = counter() print(next(gen)) # start / 0 print(gen.send("hello")) # got: hello / 1 print(next(gen)) # got: None / 2

逻辑说明:第一次next(gen)启动生成器,执行到第一个 yield,暂停并返回 0。gen.send("hello")把 hello 传回上次暂停处,赋给 got,然后继续执行到下一次 yield。第三次调用next(gen)时,没有 send 值,got 是 None。

“求值时机”是这里的高频追问点:生成器不会一口气把循环跑完,每次唤醒只前进到下一个 yield。文档若提到 yield from,我会把它理解成“委托给子生成器”,并用更复杂的模拟去验证。这四组题跑完,55 道题里的大部分概念题就会形成一个可运行的证据链。

4. 备考节奏:两遍盲答和“一页错题本”让 55 道题变成能力

题库文档最大的陷阱是“把记忆当掌握”。要避免它,我推荐两遍复习法:第一遍完全不看答案,第二遍再对照文档,把答案改写成自己的推导。节奏控制得好,一份 55 道题的文档能撑住两到三周的冲刺复习。

4.1 第一遍:只看题号,不碰答案,先盲答

第一遍只打开文档的题干部分,花几十到一百毫秒判断“我会不会”,然后在纸上写要点。会就写答案逻辑,不会就写“不会”,千万别偷看答案。我一般给自己定的规则是:每题先想一分钟,想不出来就跳过。盲答的意义是建立真实的基线,否则你会误以为所有题都简单。

对 A 类题,我选择用 IDE 补写最小例子,而不是只在纸上写。比如装饰器题,我会关掉文档完整敲一遍;生成器题,我会写一个两行的 yield 函数跑一次。对 B、C 类题,纸上写关键词就够了。第一遍不快,但能让你知道哪里是真正欠账。

4.2 第二遍:对照文档答案,重写“所以”而不是抄“是什么”

第二遍打开答案,先读,再关掉文档,用一句话重新解释。这个“解释”必须包含因果:为什么结果是这个,而不是背下来的表面现象。例如“可变默认参数”的答案不能只写“不要用可变对象”,要补充“def 执行时只创建一次默认对象,共享导致累积”。

文档的答案通常很短,最多一两句话。这远远不够撑起真实面试。我一般会把每道 A 类题扩展成完整的推导过程:先用一句话回答,再给一个代码证据,最后说清适用边界。扩展内容写进自评表的“题干变体”一栏。

4.3 错题本:只抄错题不够,还要记变体

很多人的错题本只有题目和正确答案,回头再看时只是重新读了一遍,没有新信息。我建议记录四个要素:原有错因、考点本质、面试官可能的追加方向、回看日期。

主题第一次错因考点本质面试官追加问题回看日期
GIL只答“同一时刻只执行一个线程”解释是字节码执行层面的串行,不是应用程序层面多线程 IO 密集场景为什么还有用?第3天

回看节奏我会按第 2 天、第 7 天、第 21 天三轮。第 2 天看是否真记住了,第 7 天看是否能解释,第 21 天看是否能即时说出来。错题本不在美观,而在每一条都能触发一次“重新推导”。

5. 避坑指南:翻答案文档时我踩过的 4 个坑

这一章写给所有准备用这类题库文档做冲刺的人。以下四条不是理论,是我见过、也亲历过的真实翻车现场。

5.1 坑:背了答案却写不出代码

现象:文档里装饰器示例抄了十遍,面试现场让白板写一个带参数装饰器,二十分钟只写出一个 return。
原因:看代码和写代码属于两种记忆。文档答案因为短,会让人产生“我已经会了”的错觉,实际动手时连函数嵌套层级都会列错。
解决:每次看完答案,关掉文档,空手敲一遍。敲不出来就立刻回到文档重新理解,而不是把原文再抄一遍。能跑通、能打印出预期输出,才算真正掌握这一题。

5.2 坑:记住了“是什么”,接不住“为什么”

现象:考到深拷贝只回答“浅拷贝拷贝一层,深拷贝递归拷贝所有层”,再被问“嵌套对象里为什么会有共享引用”就答不出来。
原因:题库答案通常是结论式短句,目的是快速回忆,不是深入解释。但面试官要验证的是你理解模型,而不是背结论。
解决:给每道 A 类题做一次“为什么”练习。用代码打印 id,用异常堆栈倒推执行顺序。遇到说不清的地方,用官方文档把该知识点重新过一遍。55 道题里任何一道结论,都应该能用“因为对象在内存中如何存在”来解释。

5.3 坑:所有题平均用力,时间花在偏门上

现象:文档里有 3 道关于算法题或冷门标准库的题,就把大量时间耗在上面,结果最基础的列表推导式反而一点也不稳。
原因:文档里的 55 道题按题号排列,不代表面试按相同概率抽题。基础语法才是出现频率最高的部分,冷门题更多是拉分和考察知识边界。
解决:回到自评表,A 类题优先保证手写,B 类题保证会说,C 类题只做了解。一份 55 道题的文档,前期复习重点应该落在对象模型、函数式特征和内存引用这三类,其余后置。

5.4 坑:题和项目脱节

现象:文档里的题全都会做,简历上的项目经验却没有任何一个能体现这些能力,面试官问“在项目里哪一块用到了”直接沉默。
原因:题库给你的是问题的横切面,面试官想要的是知识的纵向落地。很多人在题库上花了太多时间,忘了把答案挂到真实代码场景里。
解决:每复习完一个大类,就从自己的项目或练手 Demo 里找一个对应片段,把该知识点说成一次实际决策。比如装饰器,就说成“项目里用装饰器统一处理接口的登录校验”,把结论变成自己的经验证据。

6. 把 55 道题扩成 55 场面试对谈:追问和临场表达

题库全部过完之后,还有一个更重要的动作:把单向背题变成双向对谈。你可以拿文档里每一道题,先猜测面试官会怎么追加,再提前准备回答路径。

6.1 给每道题准备一个“追加层”

我用的方法是给文档中每道题补一个追问。下面是一个很小的对照模板:

基础答案面试官追加问题我的回答骨架
==比较值,is比较身份两个内容相同的字符串为什么可能用 is 成立先提小整数缓存或字符串驻留,再补一句“不依赖驻留去判断”
生成器能节省内存所有生成器都一定比列表快吗生成器省的是存储,不是计算;如果要反复访问,列表反而适合
GIL 让同一时刻只能执行一个线程多线程下载文件为什么还能提升速度因为 IO 等待会释放锁,而 CPU 密集场景提升有限

这样每个基础答案都有一个第二层、第三层的支点。55 道题如果都这样扩,复习量虽然变大,但面试时被追问的真实感会非常强。

6.2 口述公式:先说结论,再给代码证据,最后补边界

我给自己定的口述顺序是:一句话结论 + 一句代码证据 + 一句边界。以装饰器为例,我会这样说:装饰器是“把原函数替换成新函数”的语法糖;代码上,@decorator等价于func = decorator(func);边界是原函数元信息可能被覆盖,所以要加functools.wraps。

这个顺序对面试官来说是熟悉的,因为先结论能快速对齐认知,代码证据证明你动手过,边界证明你理解这个概念不是孤立背诵。对文档里的大部分题目,这套公式都能套进去,只是把“代码证据”换成“运行结果”或“API 行为”。

6.3 把 55 道题嵌入简历复盘

最后我会挑两三道最有代表性的题,把它们和真实写过的代码绑定。比如把可变默认参数放进“项目中处理用户缓存异常”的解释里,把生成器放进“对大文件分块读取”的描述里。这样做不是为了在面试时背故事,而是给自己一个落点:每道题的结论都能落到一个具体选择上。

我自己后来再拿到任何一份题库类文档,第一反应都不再是“背”,而是先花半小时拆题、分组、做自评表,再给每道题配一个可运行的最小示例。这样哪怕它被改成 100 道、200 道题,我也只需要复用这套流程,而不至于被题海淹没。刷题文档从来不是终点,把它拆成自己的认知体系才是。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进