Skip to content

以下是一份完整、格式正确的 Markdown 文档,涵盖您提供的全部 14 个 Python 面试题及其详细答案和拓展追问。您可以直接复制以下内容,保存为 .md 文件,使用支持 Markdown 的编辑器(如 Typora、VS Code、Obsidian 等)查看,排版清晰。


华为 OD 技术面试 · Python 八股文精讲(完整版)

字数
4515 字
阅读时间
20 分钟

本手册基于常见 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():反转(原地)

拓展追问:

  • appendextend 的区别?
    append 将参数作为整体添加(作为一个元素),extend 将可迭代对象的每个元素逐个添加。

  • remove 删除所有匹配项还是只删第一个?
    → 只删除第一个匹配项,若想删除全部可用循环或列表推导式。

  • joinsplit 的性能优劣?
    join 比多次 += 拼接字符串高效,因为字符串不可变,+= 会生成新对象;split 按分隔符切分。


4. 面向对象三大特征:封装、继承、多态;重写与重载

  • 封装:将数据(属性)和行为(方法)包装在类内部,对外提供公共接口,隐藏内部实现细节。

  • 继承:子类可以复用父类的属性和方法,支持单继承(Python 支持多继承)。

  • 多态:同一个方法在不同子类中表现不同行为(如 animal.speak(),猫叫“喵”,狗叫“汪”)。

  • 重写 (Override):子类重新定义父类的方法,方法名、参数列表相同,实现不同。
    例:class Dog(Animal): def speak(self): return "Woof"

  • 重载 (Overload):在同一个类中定义多个同名方法,但参数列表不同(类型或数量)。
    Python 不支持传统的方法重载(因为函数参数默认是动态类型),但可通过 *args**kwargsfunctools.singledispatch 模拟。

拓展追问:

  • Python 中的 super() 作用?
    → 调用父类方法,用于在子类中扩展父类功能。

  • 多继承时方法解析顺序(MRO)如何确定?
    → 使用 C3 线性化算法,可通过 类.__mro__ 查看顺序。

  • 重写和重载的异同点?
    → 重写发生在父子类之间,重载发生在同一类中;重写参数列表通常相同,重载参数列表不同;Python 不支持重载但可模拟。


5. Lambda 匿名函数

lambda 是一种表达式,返回一个函数对象,通常用于简单逻辑。

题目解答:

  1. 计算两个数的平方和:

    python
    square_sum = lambda a, b: a**2 + b**2
    print(square_sum(3, 4))  # 25
  2. 使用 filter() 筛选偶数:

    python
    nums = [1, 2, 3, 4, 5, 6]
    evens = list(filter(lambda x: x % 2 == 0, nums))
    print(evens)  # [2, 4, 6]
  3. 使用 map() 求平方:

    python
    nums = [1, 2, 3, 4, 5]
    squares = list(map(lambda x: x**2, nums))
    print(squares)  # [1, 4, 9, 16, 25]

拓展追问:

  • lambda 和普通函数的主要区别?
    lambda 只能包含一个表达式,不能包含语句或复杂逻辑;普通函数可以有多个语句、文档字符串。

  • lambda 常见使用场景?
    → 作为 sortkey 参数、map/filter/reduce 的临时函数、GUI 事件回调等。

  • 匿名函数如何捕获外部变量?
    → 类似闭包,可捕获自由变量,但注意延迟绑定问题(循环中的 lambda 可结合默认参数解决)。


6. 深浅拷贝

  • 浅拷贝 (copy.copy()):只复制最外层对象本身,内部元素仅复制引用(即指向同一个子对象)。
    对于嵌套可变对象,修改子对象会影响原对象。
  • 深拷贝 (copy.deepcopy()):递归复制所有层次的内部对象,新对象与原对象完全独立。

示例说明:

python
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
  • 自定义装饰器(如计时装饰器)

示例

python
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. 1-10 的平方列表:

    python
    squares = [i**2 for i in range(1, 11)]
    # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
  2. 1-10 的偶数平方列表:

    python
    even_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 条件}

题目解答:

  1. 从两个列表生成字典(使用 zip):

    python
    keys = ['a', 'b', 'c']
    values = [1, 2, 3]
    d = {k: v for k, v in zip(keys, values)}
    # {'a': 1, 'b': 2, 'c': 3}
  2. 给字典 dd 的值 +1:

    python
    dd = {'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

题目解答(已给),补充一个示例:

python
# 嵌套三元
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

  • 三元运算符的优先级?
    → 低于 orand,高于 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
机器学习/AItensorflow, 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 个实际应用,说明用途。

  • requestsurllib 的区别?
    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 个),返回新字符串。

示例

python
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.finditerfindall 的区别?
    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 返回,保留函数状态。

关系:生成器是迭代器,迭代器是可迭代对象,但反过来不成立。

示例

python
# 生成器函数
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 实现

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 语法。您可以直接复制使用。如果仍有显示异常,请告知具体位置,我会进一步调整。

贡献者

The avatar of contributor named as freeway348 freeway348

文件历史

撰写