Python 面试题
Python 面试会测试你对核心语言特性、数据结构、算法和面向对象编程的理解。通常包括概念性问题以评估你的知识深度,以及动手编码问题来评估实践技能。无论你是初级还是高级开发者,掌握这些主题对成功至关重要。
Python 面试涵盖内容
核心语法与数据结构
关于 Python 内置类型(如列表、字典、集合、元组)和字符串操作的问题,包括时间复杂度。
面向对象编程 (OOP)
涵盖类、继承、多态以及特殊方法,如 __init__、__str__ 和属性装饰器。
函数式编程与推导式
包括 lambda、map、filter、reduce、列表/字典推导式和生成器表达式。
并发与性能
主题如 GIL、线程与多进程对比、async/await 和性能分析。
Python 面试题示例
- Python 的主要特性是什么?好回答应覆盖
- 动态类型与动态绑定
- 解释型语言,交互式编程
- 丰富的标准库与第三方库
- 面向对象、函数式编程支持
- 可扩展性(C/C++)
查看范例答案
Python 是一种高级、解释型、动态类型的编程语言。其主要特性包括:1) 动态类型,变量无需声明类型;2) 自动内存管理(垃圾回收);3) 简洁易读的语法,强调代码可读性;4) 支持多种编程范式,如面向对象、函数式、命令式;5) 拥有庞大的标准库和活跃的第三方库生态;6) 可嵌入性和可扩展性,允许用 C/C++ 编写扩展;7) 跨平台运行。这些特性使 Python 适用于快速开发、数据科学、Web 开发等领域。
- 解释列表与元组的区别。什么时候你会使用哪一个?好回答应覆盖
- 列表可变,元组不可变
- 列表有 append、remove 等方法,元组没有
- 元组可用于字典键,列表不行
- 列表性能略差于元组(动态数组 vs 静态结构)
- 元组通常用于异构数据,列表用于同质序列
查看范例答案
列表(list)和元组(tuple)都是有序序列,但列表是可变的(增删改),元组是不可变的。这意味着列表在创建后可以修改内容,而元组不能。因此,当需要存储不可变的数据(如字典的键、函数参数包)时,使用元组;当需要频繁修改序列(如追加、删除)时,使用列表。元组通常用于存储异构数据(如坐标 (x, y)),而列表常用于同质数据(如姓名列表)。由于元组不可变,其哈希值稳定,可作为字典键或集合元素;列表则不能。性能上,元组略快于列表,但差异通常可忽略。选择时优先考虑语义:需要不变性则用元组,否则用列表。
- Python 如何管理内存?讨论垃圾回收和引用计数。好回答应覆盖
- 引用计数为主,标记-清除回收循环引用
- 每个对象有引用计数,为0时立即回收
- 分代回收处理循环引用(年轻代、老年代)
- 手动触发 gc.collect()
- 弱引用避免循环引用(weakref)
查看范例答案
Python 的内存管理通过私有堆空间实现,由解释器负责。核心机制是引用计数:每个对象维护一个计数值,记录指向它的引用数;当计数值降为0时,内存立即被释放。但引用计数无法处理循环引用(如两个对象互相引用),因此 Python 引入了垃圾回收器(gc 模块),使用标记-清除算法定期检测并回收不可达的循环引用对象。此外,分代回收将对象分为三代(年轻代、中年、老年代),新对象分配在年轻代,存活时间越长代数越高,回收频率越低,以提高效率。开发者可通过 gc 模块手动触发回收,或使用 weakref 创建弱引用避免干扰计数。这种混合策略在大多数场景下表现良好,但需注意大量对象创建可能引发性能开销。
- 编写一个函数,在不使用排序的情况下找到列表中第二大的数。好回答应覆盖
- 线性扫描,记录最大和第二大
- 处理重复值情况
- 一次遍历 O(n) 时间,O(1)空间
- 边界条件:列表长度小于2
查看范例答案
以下函数在不使用排序的情况下找到列表中第二大的数。它通过一次遍历维护两个变量:最大值和第二大值。初始化为负无穷(或列表前两个元素),然后逐个比较更新。注意处理重复值的情况:当遇到等于最大值的数时,不更新第二大值。该算法时间复杂度 O(n),空间复杂度 O(1)。如果列表长度小于2,则返回 None。
参考代码python def second_largest(nums): """ 返回列表中第二大的数,若不足两个元素则返回 None。 时间复杂度 O(n),空间复杂度 O(1)。 """ if len(nums) < 2: return None # 初始化最大值和第二大值 first = second = float('-inf') for num in nums: if num > first: second = first first = num elif num > second and num != first: second = num return second if second != float('-inf') else None - 实现一个装饰器,用于测量函数的执行时间。好回答应覆盖
- 使用 functools.wraps 保留原函数元数据
- 装饰器接收函数,返回包装函数
- 测量时间:time.time() 或 time.perf_counter()
- 处理被装饰函数有参数和返回值
- 可使用 @timeit 语法糖
查看范例答案
以下装饰器测量函数执行时间。它使用 functools.wraps 保持原函数的元信息(如 __name__、__doc__)。内部使用 time.perf_counter() 获取高精度时间,计算差值并打印。装饰器返回 wrapper 函数,该函数调用原函数并返回其结果。此装饰器可用于任何同步函数。注意避免在异步函数上直接使用。
参考代码python import time from functools import wraps def timeit(func): """ 装饰器:打印函数执行时间。 用法:@timeit def my_func(...): ... """ @wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) end = time.perf_counter() print(f"{func.__name__} took {end - start:.6f} seconds") return result return wrapper - 什么是 GIL?它如何影响多线程?好回答应覆盖
- GIL 是全局解释器锁,确保同一时刻只有一个线程执行 Python 字节码
- 影响 CPU 密集型多线程性能(不能利用多核)
- I/O 密集型任务受 GIL 影响较小(释放锁等待 I/O)
- 可通过多进程(multiprocessing)或 C 扩展绕过 GIL
- Python 3 改进了 GIL 调度,但仍存在
查看范例答案
GIL(Global Interpreter Lock)是 CPython 解释器中的一个互斥锁,它保证在任何时刻只有一个线程执行 Python 字节码。这简化了内存管理和对象访问的线程安全性,但导致多线程无法充分利用多核 CPU 进行并行计算。对于 CPU 密集型任务,多线程性能甚至可能不如单线程(因锁竞争);而对于 I/O 密集型任务,由于线程常在等待 I/O 时释放 GIL,因此仍能获得并发优势。要突破 GIL 限制,可采用多进程(multiprocessing 模块)或使用 C 扩展(如 NumPy)在释放 GIL 的情况下执行计算。Python 3.2 后引入新的 GIL 实现,减少了竞争,但 GIL 依然存在。
- 编写一个生成器,生成直到 n 的斐波那契数。好回答应覆盖
- 生成器使用 yield 产生值,保存状态
- 惰性求值,节省内存
- 斐波那契生成器逐次产生数列
- 限制数量 n 或直到超过 n
- 示例:for fib in fibonacci(10): print(fib)
查看范例答案
以下生成器生成直到 n 的斐波那契数。它使用 yield 逐个产生斐波那契数列的值,直到下一个值超过 n。生成器在每次 yield 后暂停,消耗内存小,适合处理无限序列或大数据。使用循环调用生成器即可打印结果。
参考代码python def fibonacci(n): """ 生成器:生成小于等于 n 的斐波那契数。 时间复杂度 O(k),k 为生成的项数;空间复杂度 O(1)。 """ a, b = 0, 1 while a <= n: yield a a, b = b, a + b - 解释 Python 中的方法解析顺序 (MRO) 以及 super() 是如何工作的。好回答应覆盖
- MRO 确定方法解析顺序(C3 线性化)
- Python 2.3 后使用 C3 算法,保证单调性
- super() 用于委托父类方法,遵循 MRO
- 避免钻石继承中的重复调用
- 使用 __mro__ 属性查看
查看范例答案
MRO(Method Resolution Order)是 Python 中决定类方法查找顺序的算法。Python 3 使用 C3 线性化算法,它确保子类优先于父类,父类按照继承顺序,且保持单调性(若类 A 在类 B 之前,则所有子类中 A 仍在 B 之前)。可以通过类名.__mro__ 查看。super() 函数用于调用父类(或兄弟类)的方法,它根据 MRO 返回一个代理对象,在调用方法时沿着 MRO 链查找下一个类。在多重继承中,super() 能协调调用顺序,避免重复执行。例如,在钻石继承中,super() 确保每个类的方法只被调用一次。正确使用 super() 需要所有相关类采用相同的签名。
如何准备
- 掌握内置函数和标准库(例如 itertools、collections)。
- 在 LeetCode 或 HackerRank 等平台上练习编码问题,重点关注 Python 特有的解决方案。
- 理解 Python 的内存模型以及对象是如何传递的(引用 vs 值)。
- 能够解释设计模式和 Pythonic 惯用语法(例如上下文管理器、装饰器)。
- 复习重要的数据结构:字典、集合及其时间复杂度。
常见问题
`==` 和 `is` 有什么区别?
`==` 检查值相等性;`is` 检查身份(同一对象)。对于单例如 None 使用 `is`。
如何在 Python 中处理异常?
使用 try/except 块;可选择 else 和 finally 进行清理。避免使用裸露的 except。
Python 的命名空间和作用域规则是什么?
LEGB 规则:局部、封闭、全局、内置。变量按该顺序查找。
什么是生成器,它与列表有何不同?
生成器使用 yield 惰性生成项;它们不会将所有值存储在内存中,适合处理大数据。
如何优化 Python 代码以提高性能?
使用内置函数(例如 map、filter)、列表推导式、避免全局查找、使用性能分析工具,并考虑 C 扩展。
练习 Python 题目,即时获取 AI 反馈
上传简历,获得个性化模拟面试,并了解需要改进的地方——免费开始。