简介:面向Python开发岗位求职者、在校生及需要系统梳理基础知识的转行者,这份Python面试题及答案共55道以问答形式覆盖语法、数据结构、函数、模块、异常处理、面向对象、文件操作、装饰器等高频考点。每道题均给出题目描述、代码示例、运行结果与解析,针对变量作用域、字典修改、装饰器执行顺序、finally执行时机等易错点提供了清晰结论。资源为1个docx文档,压缩包仅80KB,内容紧凑,便于打印或导入笔记软件按章节学习。已有1106人学习使用,适合笔试前集中刷题和面试前快速复盘。文档还包含数组去重求和的多种解法对比、循环嵌套枚举数字组合的完整实现,以及目录遍历、正则匹配、日志模块、JSON序列化等实用知识点,能帮助读者在掌握题型的同时深入理解Python底层运行逻辑,补齐常见知识盲区。
1. 为什么是“55道”而不是“500道”:Python 面试题文档的定位
大多数人在面试前下载《Python面试题及答案共55道.docx》这类文档,第一反应是题量不大。但刷过 500 道的题库再回来看 55 道,会发现每道题都代表一类考点:基础语法、深浅拷贝、闭包与装饰器、GIL、内存管理、生成器、并发选型、数据库与网络,每一道背后都有可以继续追问的技术面。这个体量适合三类人:准备面试的候选人拿它当自测清单;负责出题的面试官拿它当考点分布参考;带新人的工程师拿它做出岗前摸底。下面分两条线走:一条讲怎么用 python-docx 生成、解析这份文档,并把它改造成复习工具;另一条讲 55 道题里真正拉开差距的考点与答案写法,最后落到面试前 24 小时怎么用脚本把文档变成抽题机。
2. 用 python-docx 生成《Python面试题及答案共55道.docx》并反解结构
手工维护一份 55 道题的 Word 文档,最痛的不是写题目,而是改题号、调样式、对齐答案。常见做法是把题目和答案放到 Python 数据结构里,脚本统一生成 docx,以后只改数据不动格式。环境上只需要 pip install python-docx,Python 3.8 以上的解释器都能直接跑,Windows 和 macOS 行为一致。
2.1 最小可用脚本:题号、题目与答案一次写入
from docx import Document
from docx.shared import Pt
questions = [
("可变对象与不可变对象的区别?",
"不可变对象(int、str、tuple)的值创建后不能修改,任何修改都会生成新对象;"
"可变对象(list、dict、set)可以在原内存地址上直接修改。"),
("is 和 == 有什么区别?",
"== 比较两个对象的值是否相等;is 比较两个变量是否指向同一个内存地址。"),
("GIL 是什么?",
"GIL 是 CPython 解释器中的全局锁,保证同一时刻只有一个线程执行 Python 字节码。"),
]
doc = Document()
doc.add_heading("Python 面试题及答案", level=0)
for i, (q, a) in enumerate(questions, start=1):
doc.add_heading(f"{i:02d}. {q}", level=1)
p = doc.add_paragraph()
run = p.add_run("答案:")
run.bold = True
doc.add_paragraph(a)
doc.save("Python面试题及答案共55道.docx")
enumerate(start=1) 让题号从 1 开始; {i:02d} 把题号补成两位,"10." 在排序和目录里不会排到 "2." 前面。add_heading 的 level 参数决定标题层级:level=0 是文档主标题,level=1 是每题标题,Word 里基于标题样式一键生成导航,复习时可以只看题目不展开答案。答案行前面单独加一个加粗的 "答案:" 前缀,是为了后续脚本解析时能靠它定位答案起点。
这里有个容易忽略的细节:questions 列表是纯 Python 对象,里面的中文引号和顿号不需要转义,docx 内部会按 XML 规则处理;但数据源如果是外部文本文件,必须存成 UTF-8,否则解析阶段会出现乱码。
2.2 反向解析:把已有文档抽成结构化列表
拿到一份别人整理的《Python面试题及答案共55道.docx》时,格式往往不统一,直接复制粘贴整理很费时间。更可靠的做法是读回 docx,把题目和答案变成 Python 列表,之后再抽题、统计、导出 JSON 都方便。
from docx import Document
doc = Document("Python面试题及答案共55道.docx")
items = []
cur = None
for para in doc.paragraphs:
text = para.text.strip()
if not text:
continue
head = text[:4].replace(".", "").replace(" ", "")
if head.isdigit():
if cur:
items.append(cur)
cur = {"q": text, "a": []}
elif cur is not None and "答案" in text[:6]:
continue
elif cur is not None:
cur["a"].append(text)
if cur:
items.append(cur)
print(f"解析到 {len(items)} 道题")
判断逻辑按顺序解释:第一步去掉空行;第二步用 text[:4] 去掉点和空格后判断是否为题号,命中就关闭上一题、开启新题;第三步跳过以 "答案"、"参考答案" 开头的前缀行,避免把标记行收进答案;第四步把其余普通段落并入当前题目的 a 列表,答案跨多行也能完整收集。循环结束后 items 里最后一个题目还没被 append,所以循环外面补一次 if cur 的判断。
text[:4] 同时兼容 "01." 和 "10." 两种题号;如果文档用 "1、" 编号,把 replace(".", "") 改成 replace("、", "") 即可。解析完成后建议马上导出 JSON,后续所有工具都读 JSON 而不是反复解析 docx 文件:
import json
with open("questions.json", "w", encoding="utf-8") as f:
json.dump(items, f, ensure_ascii=False, indent=2)
ensure_ascii=False 保证中文以原文写入,而不是一堆 \uXXXX 转义;indent=2 方便用文本工具直接检查内容。
提示:解析脚本只对“题目 + 答案”的线性结构生效。如果文档里混有“备注”“答题模板”等其他章节,先做一次格式规整再跑脚本。
2.3 中文字体、代码底纹与题号对齐的三个必调参数
docx 默认样式在 Windows 下是等线加 Calibri,中文标题显示没问题,但代码示例的缩进和等宽效果很差。生成文档时建议把下面几个参数封装成模板函数:
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
from docx.shared import Pt
def set_chinese_font(doc, name="微软雅黑", size=11):
style = doc.styles["Normal"]
style.font.name = "Calibri"
style.font.size = Pt(size)
style.element.rPr.rFonts.set(qn("w:eastAsia"), name)
def add_code_block(doc, code):
p = doc.add_paragraph()
pPr = p._p.get_or_add_pPr()
shd = OxmlElement("w:shd")
shd.set(qn("w:val"), "clear")
shd.set(qn("w:fill"), "F2F2F2")
pPr.append(shd)
run = p.add_run(code)
run.font.name = "Consolas"
run.font.size = Pt(9)
return p
| 参数 | 作用 | 常用值 |
|---|---|---|
font.name | 控制西文字体 | Calibri / Consolas |
qn("w:eastAsia") | 设置中文字体键 | 微软雅黑 |
w:fill | 段落底纹颜色 | F2F2F2 |
Pt(size) | 字号 | 9(代码) / 11(正文) |
set_chinese_font 里 font.name 只控制西文字体,中文字体必须通过 qn("w:eastAsia") 设置到 OOXML 命名空间的 rFonts 节点上,漏掉它 Word 会用默认主题字体渲染中文,导出 PDF 时字体也会错乱。add_code_block 通过 pPr 追加 w:shd 节点给整个段落加浅灰底纹;代码内容用 Consolas 等宽字体,缩进在任意字号下都能对齐。题号建议始终用 {i:02d} 格式化,不要手工补空格,手工空格在不同字号和行距下宽度不一致,目录里看起来参差不齐。
3. Python 面试题考点分布与高频题解析
55 道题的价值不在数量,而在覆盖结构。如果 55 道全部集中在列表和字典,面试官根本问不到并发和内存层。常见的做法是先定模块比例,再往每个格子里填具体题目,比例定好之后,每道题还要能代表一类追问,而不是孤立的记忆点,复习才能举一反三。
3.1 模块分配表:从 JD 关键字与真实面经反推
模块比例我一般用两个来源反推:近期职位 JD 上出现频率最高的技能词,以及面经里被追问超过两轮的知识点。按这个口径,一份可直接套用的 55 道分配如下:
| 模块 | 题数 | 占比 | 代表题 |
|---|---|---|---|
| 基础语法与内置类型 | 12 | 22% | is 与 ==、可变/不可变、深浅拷贝 |
| 数据结构与算法 | 8 | 15% | 字典排序、列表推导式、set 去重 |
| 函数与装饰器 | 8 | 15% | 闭包、装饰器、*args/**kwargs |
| 面向对象 | 7 | 13% | MRO、property、 slots |
| 并发与性能 | 6 | 11% | GIL、线程/进程/协程、async/await |
| 内存与异常 | 5 | 9% | 引用计数、垃圾回收、异常链 |
| 网络与数据库 | 5 | 9% | requests 超时、MySQL 连接池、Redis 过期策略 |
| 工程化与框架 | 4 | 7% | 虚拟环境、Flask/Django 差异 |
12、8、8、7、6、5、5、4 加起来正好是 55。基础语法占 22% 有两个原因:一面普遍先用语言基础做筛选,答错一道往往直接终止流程;基础题适合快速追问,考完 is 与 == 可以顺势问 id() 和整数缓存。面向中高级岗位,可以把工程化模块从 4 道加到 6 道,从并发模块匀出 2 道,因为高级岗位更看重排查问题的路径,而不是语法记忆。
3.2 可变对象与 is/==:一道题看出是否理解对象模型
a = []
b = []
print(a == b) # True,值相等
print(a is b) # False,不是同一个对象
c = a
a.append(1)
print(c) # [1],c 与 a 指向同一 list
== 调用对象的 eq ,比较内部元素;is 比较 id() 是否相同。最容易记混的是小整数缓存:CPython 在启动时预创建 -5 到 256 的整数对象,所以 x = 256; y = 256; x is y 返回 True,改成 257 返回值就不一定。回答到这里,面试官通常追加一句“字符串能不能用 is 判断相等”。正确的答法是:短字符串可能被 intern,但 intern 策略依赖解释器实现和编译优化,业务代码里判断值必须用 ==。这道题的易错点不在语法,而在把“恰好为 True”当成“规则如此”。
3.3 闭包与装饰器:functools.wraps 为什么不能省
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
print(f"{func.__name__} 耗时 {time.perf_counter() - start:.4f}s")
return result
return wrapper
@timer
def load_data(n: int) -> list:
"""加载 n 条数据"""
return [i * 2 for i in range(n)]
答题要点分两层。第一层是闭包:wrapper 通过自由变量访问 func,函数对象和它的环境一起被保存下来,这就是装饰器能拦截调用的基础。第二层是 functools.wraps:它把原始函数的 name 、 doc 、 wrapped 等属性复制到 wrapper 上,不写 wraps 的话 help(load_data) 显示的是 wrapper 的签名和空文档说明,日志系统按函数名聚合耗时也会出错。 *args, **kwargs 负责透传原函数参数,漏掉任意一个,被装饰函数传参调用就会 TypeError。追问环节出现频率高的是带参数的装饰器,那需要三层嵌套:最外层接收装饰器参数,中间层接收函数,内层 wrapper 接收调用参数。55 道题如果只留一道装饰器题,留这一道。
3.4 GIL 与并发选型:多线程、多进程、协程的边界
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
def cpu_task(n: int) -> int:
return sum(i * i for i in range(n))
def io_task(url: str) -> int:
return len(url) # 实际场景里是 requests.get(url).status_code
with ProcessPoolExecutor(max_workers=4) as pool:
results = list(pool.map(cpu_task, [10000] * 4))
with ThreadPoolExecutor(max_workers=8) as pool:
results = list(pool.map(io_task, ["https://a.com"] * 8))
GIL 的全称是 Global Interpreter Lock,CPython 靠它保护引用计数的线程安全,代价是同一个时刻只能有一个线程执行字节码。常见的错误结论是“有 GIL 就别用线程”:实际上 I/O 操作和 time.sleep 都会释放 GIL,URL 请求、文件读写这类等待型任务用 ThreadPoolExecutor 是合理选择,进程创建成本远高于线程。CPU 密集型任务才需要多进程,ProcessPoolExecutor 每个子进程独立持有解释器和 GIL,能真正用满多核。pool.map 的两个参数值得记:max_workers 控制池大小,进程池默认取 CPU 核数,线程池在 I/O 密集场景可以调到核数的 4 到 8 倍;map 返回结果的顺序与传入顺序一致,但执行顺序不保证,要按完成先后处理结果需要换成 as_completed。
4. 面试题答案的四段式写法与 ast 自动校验
一道题答得好不好,答案的写法比知识点本身更影响复习效率。整段文字是最难用的答案形式:背不住,也抓不住追问方向。55 道题的文档要服务二十多个小时的反复翻阅,答案体例统一,复习才有节奏。
4.1 四段式答案模板
维护题库时统一用四段结构:
- 结论句:一句话直接回答,20 字以内。比如“GIL 是 CPython 解释器层面的互斥锁”。
- 原理:2 到 3 句解释为什么,补充触发条件。
- 代码:5 到 10 行可运行示例,注释里写明输出。
- 易错点:一句话提示面试官会往哪个方向追问。
| 段位 | 内容 | 长度 |
|---|---|---|
| 1 结论句 | 直接回答,不铺垫 | 20 字内 |
| 2 原理 | 为什么、触发条件 | 2 到 3 句 |
| 3 代码 | 可运行示例,注释写输出 | 5 到 10 行 |
| 4 易错点 | 追问方向 | 1 句 |
以“垃圾回收”为例,结论句是“CPython 以引用计数为主,标记-清除和分代回收处理循环引用”;原理说明引用计数归零立即释放,但循环引用会让计数永远大于 0;代码给两个对象互相引用的演示;易错点写“weakref 可以打断循环引用”。这样一份答案,复习时 30 秒内能复述全貌,作为面试官时也能顺着易错点设计连续追问。
4.2 ast 查语法,subprocess 验运行
55 道题的答案里有大量代码示例,人工核对不现实。校验分两步:先 ast.parse 查语法,再 subprocess 起独立进程跑结果。
import ast
import subprocess
import sys
def check_syntax(code: str) -> bool:
try:
ast.parse(code)
return True
except SyntaxError as err:
print(f"语法错误: {err}")
return False
samples = [
"print([i * 2 for i in range(3)])",
"def f(x): return x if x > 0", # 缺 else,语法不完整
]
for code in samples:
print(code, "->", check_syntax(code))
def run_code(code: str, timeout: int = 5) -> str:
proc = subprocess.run(
[sys.executable, "-c", code],
capture_output=True, text=True, timeout=timeout,
)
return proc.stdout
ast.parse 只构建语法树不执行,任何语法错误都能捕获,55 段代码全量校验不到 1 秒。语法通过不代表逻辑正确,所以对答案里带 print 的示例,用 subprocess.run 起一个独立 Python 进程执行,timeout=5 防止死循环把校验脚本拖死,capture_output=True 收集标准输出,text=True 把字节流转成字符串。这里刻意不用 exec 直接执行,因为答案代码可能有 input 或长时间运行的任务,子进程能安全回收,异常也不会污染校验主进程。
提示:subprocess 执行的是真实代码,答案里有文件写入或网络请求时,timeout 之外还要限定工作目录,建议放到临时目录里跑。
4.3 快速体检:统计标题数量、题号连续性和答案覆盖
生成完文档后做一次结构化体检,比肉眼翻页靠谱:
import re
from docx import Document
doc = Document("Python面试题及答案共55道.docx")
h1 = [p.text for p in doc.paragraphs if p.style.name == "Heading 1"]
nums = []
for text in h1:
m = re.match(r"(\d+)\.", text)
if m:
nums.append(int(m.group(1)))
print(f"题目总数: {len(h1)}")
print(f"题号连续: {nums == list(range(1, len(nums) + 1))}")
missing = set(range(1, len(nums) + 1)) - set(nums)
print(f"缺失题号: {sorted(missing) if missing else '无'}")
p.style.name 过滤出 Heading 1,也就是题库里的题目标题,其他段落不会混进来;re.match(r"(\d+).") 提取行首题号,与 range(1, n+1) 比较可以一次性发现漏号、重号和乱序。答案覆盖检查同样用 2.2 的解析结果:遍历 items,把 a 为空或长度小于 10 的题目单独输出到一个文件,那就是下个版本要补写的清单。
5. 面试前 24 小时:把 docx 变成随机抽题机
第 4 章的 JSON 和统计产出到这里派上用场。面试前不要从头到尾翻 docx,那只会停留在“看着都眼熟”的错觉里,正确的用法是把它变成随机抽题机,让机器替自己挑盲区。
5.1 先答后看:input 阻塞实现自测节奏
import random
import json
with open("questions.json", encoding="utf-8") as f:
items = json.load(f)
sample = random.sample(items, k=min(10, len(items)))
for i, item in enumerate(sample, 1):
input(f"第 {i} 题: {item['q']}\n想好答案后按回车")
print("答案:", item["a"], "\n---")
random.sample 不重复抽取,k=10 对应一轮 30 分钟左右的自测。核心设计是 input() 阻塞:题目亮出来后强迫自己先组织语言,按回车才显示答案,这个节奏比直接看答案有效得多。把脚本放进循环,每一轮抽到的题集合不同,55 道题的覆盖会随轮次自然摊开。
5.2 答不上的题落进 review 清单,第二天再抽
weak = []
for i, item in enumerate(sample, 1):
answer = input(f"第 {i} 题: {item['q']}\n答得出按 1,答不出按 0: ")
if answer.strip() == "0":
weak.append(i)
if weak:
with open("review.txt", "w", encoding="utf-8") as f:
for idx in weak:
f.write(f"{sample[idx - 1]['q']}\t{sample[idx - 1]['a']}\n")
print(f"已写入 {len(weak)} 道薄弱题,见 review.txt")
这里传进 weak 的是 sample 里的位置下标,写文件时用 sample[idx - 1] 取回原题。第二天把 review.txt 里的题目作为必答项优先测完,再跑随机抽题,两个文件对照就能看出薄弱点是否真的收敛。把 review 里的题号跟 4.3 的题号集合取交集,还能定位到具体模块,如果全是并发模块漏答,就该回头翻 GIL 那几道题。
5.3 三个最容易答串的考点
自测时最先暴露的通常是这三个。默认参数陷阱: def append_item(item, lst=[]) 连续调用两次,第二次的 lst 里已有第一个元素,因为默认参数在 def 执行时求值一次,之后所有调用共享同一个 list,正确写法是默认值写 None、函数体内再初始化。切片是浅拷贝: outer = [[1], [2]]; inner = outer[:]; inner[0].append(99) 之后 outer[0] 变成 [1, 99],切片只复制外层引用,完全独立必须用 copy.deepcopy。遍历字典时修改键:直接 for k in d: del d[k] 抛 RuntimeError: dictionary changed size during iteration,正确写法是 for k in list(d): del d[k] ,先用 list(d) 固化键的副本。
这三个点单独记都不难,但连续出现在同一轮抽题里最容易答混。如果 review.txt 里同时出现两道以上这类题目,回到 4.1 的模板补强答案的易错点字段,再跑一遍 4.2 的校验脚本,第二天这份 55 道清单就能收敛到可背诵、可追问、可验证的状态。



被折叠的 条评论
为什么被折叠?



