以下是一份完整、格式正确的 Markdown 文档,涵盖您提供的全部 14 个 Python 面试题及其详细答案和拓展追问。您可以直接复制以下内容,保存为 .md 文件,使用支持 Markdown 的编辑器(如 Typora、VS Code、Obsidian 等)查看,排版清晰。
华为 OD 技术面试 · Python 八股文精讲(完整版)
本手册基于常见 Python 面试题整理,包含核心答案与拓展追问,助力高效备战。
所有代码示例均可在 Python 3.6+ 环境中运行。
1. Python 基础数据类型及可变性
| 数据类型 | 是否可变 | 说明 |
|---|---|---|
int(整数) | ❌ 不可变 | 数值对象一旦创建不可修改 |
float(浮点数) | ❌ 不可变 | 同上 |
bool(布尔) | ❌ 不可变 | True/False 是单例对象 |
str(字符串) | ❌ 不可变 | 所有字符串操作返回新字符串 |
tuple(元组) | ❌ 不可变 | 元素不可增删改,但若元素是可变对象(如列表),其内部可变 |
list(列表) | ✅ 可变 | 可增删改元素 |
dict(字典) | ✅ 可变 | 可增删改键值对 |
set(集合) | ✅ 可变 | 可增删元素,但元素必须不可变 |
frozenset(冻结集合) | ❌ 不可变 | 不可变版本的集合 |
拓展追问:
为什么字符串要设计为不可变?
→ 安全性(如哈希值稳定)、性能优化(字符串驻留)、线程安全。元组真的是完全不可变吗?
→ 元组内若包含列表,列表内容可变,但元组的引用不变,故元组本身不可变,但其元素(可变对象)的内容可以变。dict的键为什么要求不可变?
→ 因为字典基于哈希表,键需可哈希(hash()),不可变对象(如数字、字符串、元组)通常可哈希。
2. 字典、列表、元组的区别
| 对比维度 | 列表 (list) | 元组 (tuple) | 字典 (dict) |
|---|---|---|---|
| 可变性 | 可变 | 不可变 | 可变 |
| 有序性 | 有序(插入顺序) | 有序(插入顺序) | Python 3.7+ 有序(插入顺序) |
| 存储方式 | 存储元素本身 | 存储元素本身 | 存储键值对(键唯一) |
| 语法 | [1, 2] | (1, 2) | {'a':1} |
| 性能 | 内存占用适中,增删慢(需移动元素) | 内存占用小,访问快 | 哈希表,查找极快 |
| 使用场景 | 存储同类型动态数据 | 作为不可变数据结构,如函数参数、字典键 | 需要通过键快速查找的数据 |
拓展追问:
列表和元组哪个更省内存?为什么?
→ 元组更省内存,因为元组大小固定,无需预留多余空间;列表有动态扩容机制,会预留额外容量。字典的底层实现是什么?
→ Python 3.6 之前采用哈希表 + 开放寻址,3.6 之后使用紧凑的indices+entries结构,保持顺序且节省内存。如何实现有序字典?
→ 直接使用dict(Python 3.7+ 默认有序),或使用collections.OrderedDict(适用于早期版本或需要额外有序方法)。
3. 常用 Python 内置函数及字符串/列表方法
常用内置函数(不限于字符串/列表)
len()、max()、min()、sum()、sorted()、reversed()、enumerate()、zip()、map()、filter()、reduce()(来自 functools)、isinstance()、type()、id()、print()、input()、open()、range()、all()、any()、chr()、ord()。
字符串内置方法(不少于 6 个)
str.upper()/lower():大小写转换str.strip():去除首尾空白str.split():分割为列表str.join():将序列合并为字符串str.find()/index():查找子串(find返回 -1,index抛异常)str.replace():替换子串str.startswith()/endswith():判断首尾str.isdigit()/isalpha():判断字符类型
列表内置方法(增删改查,不少于 6 个)
list.append(obj):末尾添加list.insert(index, obj):指定位置插入list.extend(iterable):扩展列表list.pop([index]):删除并返回元素(默认末尾)list.remove(obj):删除第一个匹配项list.clear():清空列表list.index(obj):查找索引list.count(obj):计数list.sort():排序(原地)list.reverse():反转(原地)
拓展追问:
append和extend的区别?
→append将参数作为整体添加(作为一个元素),extend将可迭代对象的每个元素逐个添加。remove删除所有匹配项还是只删第一个?
→ 只删除第一个匹配项,若想删除全部可用循环或列表推导式。join和split的性能优劣?
→join比多次+=拼接字符串高效,因为字符串不可变,+=会生成新对象;split按分隔符切分。
4. 面向对象三大特征:封装、继承、多态;重写与重载
封装:将数据(属性)和行为(方法)包装在类内部,对外提供公共接口,隐藏内部实现细节。
继承:子类可以复用父类的属性和方法,支持单继承(Python 支持多继承)。
多态:同一个方法在不同子类中表现不同行为(如
animal.speak(),猫叫“喵”,狗叫“汪”)。重写 (Override):子类重新定义父类的方法,方法名、参数列表相同,实现不同。
例:class Dog(Animal): def speak(self): return "Woof"重载 (Overload):在同一个类中定义多个同名方法,但参数列表不同(类型或数量)。
Python 不支持传统的方法重载(因为函数参数默认是动态类型),但可通过*args、**kwargs或functools.singledispatch模拟。
拓展追问:
Python 中的
super()作用?
→ 调用父类方法,用于在子类中扩展父类功能。多继承时方法解析顺序(MRO)如何确定?
→ 使用 C3 线性化算法,可通过类.__mro__查看顺序。重写和重载的异同点?
→ 重写发生在父子类之间,重载发生在同一类中;重写参数列表通常相同,重载参数列表不同;Python 不支持重载但可模拟。
5. Lambda 匿名函数
lambda 是一种表达式,返回一个函数对象,通常用于简单逻辑。
题目解答:
计算两个数的平方和:
pythonsquare_sum = lambda a, b: a**2 + b**2 print(square_sum(3, 4)) # 25使用
filter()筛选偶数:pythonnums = [1, 2, 3, 4, 5, 6] evens = list(filter(lambda x: x % 2 == 0, nums)) print(evens) # [2, 4, 6]使用
map()求平方:pythonnums = [1, 2, 3, 4, 5] squares = list(map(lambda x: x**2, nums)) print(squares) # [1, 4, 9, 16, 25]
拓展追问:
lambda和普通函数的主要区别?
→lambda只能包含一个表达式,不能包含语句或复杂逻辑;普通函数可以有多个语句、文档字符串。lambda常见使用场景?
→ 作为sort的key参数、map/filter/reduce的临时函数、GUI 事件回调等。匿名函数如何捕获外部变量?
→ 类似闭包,可捕获自由变量,但注意延迟绑定问题(循环中的 lambda 可结合默认参数解决)。
6. 深浅拷贝
- 浅拷贝 (
copy.copy()):只复制最外层对象本身,内部元素仅复制引用(即指向同一个子对象)。
对于嵌套可变对象,修改子对象会影响原对象。 - 深拷贝 (
copy.deepcopy()):递归复制所有层次的内部对象,新对象与原对象完全独立。
示例说明:
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)
original[0].append(5) # 修改子列表
print(shallow) # [[1, 2, 5], [3, 4]] → 受影响
print(deep) # [[1, 2], [3, 4]] → 不受影响拓展追问:
如何实现自定义对象的深拷贝?
→ 定义__deepcopy__方法或使用copy.deepcopy。浅拷贝的常用场景?
→ 复制只读结构、性能敏感且不需独立子对象时。赋值(
=)与浅拷贝的区别?
→ 赋值只是增加引用,两者指向同一对象;浅拷贝创建新对象,但子对象仍共享。
7. 装饰器
定义:装饰器是一个可调用对象(函数或类),它接受一个函数作为参数,并返回一个新的函数(或原函数),在不修改原函数代码的前提下扩展功能。
为什么用装饰器:实现横切关注点(如日志、计时、权限校验、缓存等),提高代码复用性和可维护性。
常见装饰器:
- 内置:
@classmethod、@staticmethod、@property - 测试框架:
@unittest.skip、@pytest.mark.skip、@pytest.mark.parametrize、@pytest.fixture配合@pytest.mark.usefixtures - 自定义装饰器(如计时装饰器)
示例:
def timer(func):
import time
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"耗时: {time.time()-start:.4f}s")
return result
return wrapper
@timer
def slow_func():
time.sleep(1)拓展追问:
装饰器的执行顺序(多个装饰器叠加)?
→ 从下往上定义,执行时从上往下(即先装饰最内层)。如何给装饰器传递参数(带参数的装饰器)?
→ 再嵌套一层函数,外层接收参数,中层接收函数,内层封装。类装饰器与函数装饰器的区别?
→ 类装饰器通过__call__实现,可保留状态。如何保留被装饰函数的元信息(如
__name__)?
→ 使用functools.wraps装饰包装函数。
8. 列表推导式
语法:[表达式 for 变量 in 可迭代对象 if 条件]
题目解答:
1-10 的平方列表:
pythonsquares = [i**2 for i in range(1, 11)] # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]1-10 的偶数平方列表:
pythoneven_squares = [i**2 for i in range(1, 11) if i % 2 == 0] # [4, 16, 36, 64, 100]
拓展追问:
列表推导式与
map/filter的对比?
→ 列表推导式更 Pythonic、可读性高,但map/filter结合lambda在某些场景更简洁;性能差异不大,但推导式通常更快。如何写嵌套循环的列表推导式?
→[ (i,j) for i in range(3) for j in range(2) ]。列表推导式内存占用问题?
→ 它会一次性生成整个列表,大数据量时可用生成器表达式((expr for ...))节省内存。
9. 字典推导式
语法:{key_expression: value_expression for 变量 in 可迭代对象 if 条件}
题目解答:
从两个列表生成字典(使用
zip):pythonkeys = ['a', 'b', 'c'] values = [1, 2, 3] d = {k: v for k, v in zip(keys, values)} # {'a': 1, 'b': 2, 'c': 3}给字典
dd的值 +1:pythondd = {'a': 1, 'b': 2, 'c': 3} new_dd = {k: v+1 for k, v in dd.items()} # {'a': 2, 'b': 3, 'c': 4}
拓展追问:
字典推导式中的
zip有什么作用?
→ 将两个序列配对成元组序列,方便同时遍历键和值。如何过滤字典中的元素?
→ 在推导式末尾添加if 条件,如{k:v for k,v in d.items() if v > 10}。能否用字典推导式反转键值?
→ 可以,但需注意值必须可哈希且唯一,否则会覆盖。
10. 三元运算符
语法:value_if_true if condition else value_if_false
题目解答(已给),补充一个示例:
# 嵌套三元
score = 85
grade = "优秀" if score >= 90 else ("良好" if score >= 80 else ("及格" if score >= 60 else "不及格"))
print(f"成绩等级: {grade}") # 输出: 成绩等级: 良好拓展追问:
三元运算符支持链式吗?
→ 支持,但可读性差,不推荐嵌套太深。与 C 语言
? :的区别?
→ Python 是if-else顺序,不同于 C 的condition ? expr1 : expr2。三元运算符的优先级?
→ 低于or和and,高于lambda,一般建议加括号明确。
11. Python 常用库
| 类别 | 常用库 |
|---|---|
| 数据处理 | numpy, pandas, scipy |
| 可视化 | matplotlib, seaborn, plotly |
| Web 开发 | django, flask, fastapi, tornado |
| 爬虫 | requests, scrapy, beautifulsoup4, selenium |
| 自动化测试 | unittest, pytest, robotframework, selenium |
| 数据库 | sqlalchemy, pymysql, psycopg2, redis-py |
| 机器学习/AI | tensorflow, pytorch, scikit-learn, transformers |
| 系统/文件 | os, sys, shutil, pathlib, glob, json, pickle, csv |
| 并发/异步 | threading, multiprocessing, asyncio, concurrent.futures |
| 正则 | re |
| 时间 | datetime, time, calendar |
| 加密 | hashlib, hmac, jwt |
| 网络 | socket, http.server, urllib |
拓展追问:
你常用哪些库?用在什么项目?
→ 准备 2~3 个实际应用,说明用途。requests和urllib的区别?
→requests更高级,支持自动编码、Session、JSON 等,更易用。
12. re 模块中 match、search、findall、sub 的区别
| 方法 | 行为 |
|---|---|
re.match(pattern, string) | 从字符串起始位置匹配,若不匹配返回 None,只匹配一次。 |
re.search(pattern, string) | 在整个字符串中搜索第一个匹配的位置,返回 Match 对象,若找不到返回 None。 |
re.findall(pattern, string) | 返回所有非重叠匹配的子串列表(若有分组则返回分组元组列表)。 |
re.sub(pattern, repl, string, count=0) | 替换所有匹配项(或指定 count 个),返回新字符串。 |
示例:
s = "hello 123 world 456"
re.match(r'\d+', s) # None,因为开头不是数字
re.search(r'\d+', s) # 匹配 '123'
re.findall(r'\d+', s) # ['123', '456']
re.sub(r'\d+', 'X', s) # 'hello X world X'拓展追问:
re.finditer与findall的区别?
→finditer返回迭代器,每个元素是Match对象,适合大数据量时节省内存。如何忽略大小写?
→ 使用re.I标志。分组捕获如何使用?
→ 用括号分组,match.group(1)获取分组内容;findall在有分组时返回元组列表。
13. 迭代器、可迭代对象、生成器的概念
- 可迭代对象 (Iterable):实现了
__iter__()方法的对象,或者实现了__getitem__()且支持整数索引的对象(如list,str,dict,file)。可通过iter()获取迭代器。 - 迭代器 (Iterator):实现了
__iter__()和__next__()方法的对象,通过next()逐个返回元素,遍历完后抛出StopIteration。 - 生成器 (Generator):一种特殊的迭代器,使用
yield关键字定义函数,或通过生成器表达式(expr for ... in ...)创建。它延迟计算,每次next()执行到yield返回,保留函数状态。
关系:生成器是迭代器,迭代器是可迭代对象,但反过来不成立。
示例:
# 生成器函数
def fib(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a+b
# 使用
for val in fib(5):
print(val) # 0,1,1,2,3拓展追问:
生成器的优势?
→ 惰性求值,节省内存(尤其处理大数据流);可无限序列。yield from的作用?
→ 委托给另一个生成器,简化嵌套生成器的代码。如何判断一个对象是否可迭代?
→isinstance(obj, collections.abc.Iterable)。迭代器是否可重复遍历?
→ 不可重复,迭代器遍历完后会耗尽;可迭代对象每次iter()返回新迭代器。
14. 冒泡排序 Python 实现
def bubble_sort(arr):
n = len(arr)
for i in range(n - 1): # 需 n-1 轮
swapped = False
for j in range(n - 1 - i): # 每轮最大元素冒泡到最后
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
swapped = True
if not swapped: # 无交换说明已有序,提前结束
break
return arr
# 测试
nums = [64, 34, 25, 12, 22, 11, 90]
bubble_sort(nums)
print(nums) # [11, 12, 22, 25, 34, 64, 90]拓展追问:
冒泡排序的时间复杂度?
→ 最坏/平均 O(n²),最好 O(n)(已有序且用优化标志)。空间复杂度?
→ O(1) 原地排序。冒泡排序是否稳定?
→ 稳定(相等元素不交换)。有哪些比冒泡更高效的排序?
→ 快速排序、归并排序、堆排序(O(n log n));Python 内置的sorted使用 Timsort(混合算法)。如何写一个通用的排序函数支持自定义比较?
→ 利用key参数或functools.cmp_to_key。
面试准备建议
- 对每个概念不仅要背定义,最好能写代码示例。
- 结合实际项目经验,说明在哪里用到了某知识点(如装饰器用于日志、pytest 参数化)。
- 对于排序、推导式等,注意时间/空间复杂度分析。
- 面试时尽量展示对底层原理的理解(如 CPython 中字典的实现)。
祝面试顺利! 🚀
以上文档已修复所有格式问题,所有表格、代码块、列表均使用标准 Markdown 语法。您可以直接复制使用。如果仍有显示异常,请告知具体位置,我会进一步调整。