Python 编程面试基础课程
面向编程面试的结构化 Python 课程,涵盖语言基础、数据结构、函数与推导式、面向对象与异常,以及算法和面试策略,并配有练习题。
你将学到
- 解释 Python 基础、可变性、类型提示与环境配置
- 按正确时间复杂度选择和运用列表、元组、集合、字典及内置函数
- 编写高效函数、推导式、闭包、装饰器和生成器
- 运用面向对象、继承、多态、特殊方法和异常处理
- 用常见算法和可复用的策略解决面试问题
开始前准备
- 具备任意语言的编程基础
- 熟悉变量、函数和循环
- 安装 Python 3 环境或使用在线解释器练习
课程 1 Python 基础与面试心态
Python 面试看重你对变量与值之间关系的精确理解。名字只是对象的引用,所以 a = [] 然后 b = a 会让 b 指向同一个列表,而不是复制它。用 id() 和 is 检查对象身份,用 == 比较值。
分清可变类型和不可变类型可以避免经典的别名问题。列表、字典和集合可以原地修改,而整数、字符串和元组不能。把可变对象传入函数可能意外改变调用方的数据,因此要理解何时使用 copy.copy() 或 copy.deepcopy()。
面试官越来越看重类型注解,因为它体现清晰度和专业度。为函数参数和返回值添加注解,例如 def parse(text: str) -> list[int]:,必要时使用 Optional、Union 或 Any。清晰的注解比长篇注释更快传达意图。
了解面试官如何评估代码:正确性、效率、沟通和边界情况都很重要。动手前先说明思路,然后写出整洁的代码并用具体例子验证。不要背诵答案,而要展示在压力下从基本原理推理的能力。
要准备好共享编辑器或远程环境,这些环境通常已经预装 Python。练习运行 python -m pytest、使用 pdb 或 breakpoint() 调试,并用 ruff 或 black 格式化代码。顺畅的工具链能让注意力集中在题目本身。
扎实的基础知识会为整场面试定下基调。先说明假设,在脑中测试小片段的行为,并解释每一行代码存在的原因。这个基础会让后续每个主题更容易应用。
心智模型练习:理解名字、引用和对象身份。用 id() 和 is 判断身份,== 判断值,copy() 复制独立列表。用简单例子测试可变性。
示例
面试官在电话初筛中请你解释 None 的含义以及 Python 如何判断真值。你回答 None 是单例对象,而空容器、零和空字符串都属于假值。接着你提到比较时应使用 is None,以此展示扎实的 Python 基础。
示例:a = []; b = a; b.append(1) 会改变 a,因为两个名字指向同一个列表。
课程 2 选择合适的数据结构
列表是最常用的序列,因为它支持快速追加、索引、切片和遍历。lst.append(x) 和 lst.pop() 是常数时间操作,而在开头附近插入或删除是 O(n)。需要保持顺序并按下标访问时,使用列表。
元组是不可变序列,适合固定记录和字典键。单元素元组需要逗号,例如 (1,),解包可以写成 a, b = pair。当数据不应改变时,优先使用元组。
集合存储唯一元素,平均 O(1) 即可完成成员判断。使用 set()、add()、remove(),以及 &、|、- 处理交集、并集和差集。集合非常适合去重和快速存在性查询。
字典把键映射到值,平均 O(1) 完成查找、插入和删除。字符串或元组等可哈希键可以快速访问,例如 counts[key],defaultdict 或 Counter 也能简化计数。键的选择要谨慎,因为不可哈希的列表不能作为键。
内置函数常常能替代手工循环,让解法更容易阅读。len()、sorted()、enumerate()、zip()、min() 和 max() 覆盖常见面试模式,sorted(items, key=...) 提供灵活排序。要清楚每个函数返回什么,以及何时创建新对象。
面试官关注时间复杂度以及你是否为操作选择了正确的结构。列表查找是 O(n),而集合和字典平均是 O(1),排序是 O(n log n)。实现前说出取舍,让选择显得更有意识。
选型练习:有序序列用列表,固定记录和字典键用元组,去重和成员判断用集合,键值查找用字典。知道每个操作是 O(1) 还是 O(n)。
示例
你需要在一个可能包含数十万项元素的列表中频繁检查成员是否存在。你改用 set,让 item in seen 以常数时间完成,并说明这是基于哈希的查找。你还会提到,如果需要保留顺序,则要根据约束选择 dict 或列表。
示例:列表成员判断是 O(n),集合是 O(1)。
课程 3 函数、推导式与生成器
函数签名能表达调用方应该如何理解和使用代码。位置参数、默认值、仅限关键字参数以及 *args / **kwargs 在合理使用时非常灵活。要避免可变默认参数,例如 def f(items=[]),因为它们会在多次调用间共享。
*args 把多余的位置参数收集成元组,**kwargs 把多余的关键字参数收集成字典。它们在包装器和委托调用中尤其有用,可以写成 func(*args, **kwargs)。要谨慎使用,让签名保持明确。
Python 按 LEGB 解析名字:局部、外层、全局和内置作用域。除非使用 nonlocal 或 global,函数内赋值会创建局部变量。闭包可以捕获外层变量并保留状态,而不必定义类。
装饰器包装函数,在不改变核心逻辑的情况下添加行为。装饰器是接收函数并返回新函数的可调用对象,@timer 等价于 func = timer(func)。生产代码中可用 functools.wraps 保留元数据。
推导式可以简洁地构建列表、字典和集合,通常比等价循环更快。使用 [x * 2 for x in nums if x > 0]、{k: v for ...} 和 {x for x in ...} 完成可读的转换。推导式要短小,嵌套过深时不如命名循环清晰。
生成器用 yield 惰性产生值,避免在内存中构建完整集合。生成器表达式如 (x for x in data) 可与 sum()、any() 或 all() 配合。提到惰性求值,说明你了解内存和流式处理问题。
签名练习:合理使用位置参数、默认值、仅关键字参数、*args 和 **kwargs。避免可变默认参数,用推导式和生成器写简洁、省内存的代码。
示例
面试官要求你把用户 ID 列表转换为小写名称,同时避免构建巨大的中间列表。你写出 names = (name.lower() for name in users),并解释生成器表达式会惰性地产出值。你还把它与列表推导式对比,强调内存占用是关键的取舍点。
示例:def f(items=None): items = items or [] 可避免多次调用共享同一个默认列表。
课程 4 面试中的面向对象与异常处理
类把状态和行为组织在一起,实例属性通常在 __init__ 中赋值。显式使用 self,让每个方法知道自己在操作哪个对象。类属性是共享的,实例属性则按对象创建。
继承让子类复用并扩展父类行为,super().__init__() 用于调用父类初始化方法。方法重写会替换原有行为,isinstance(obj, Base) 用来检查类层级。面试中通常优先组合,而不是建立很深的继承链。
多态意味着不同对象可以共享同一接口,代码因此能跨类型工作。Python 的鸭子类型允许任何具备所需方法的对象参与,len() 通过 __len__ 工作,迭代通过 __iter__ 工作。设计时应面向接口,而不是具体类。
魔术方法可以定制内置行为:__repr__、__str__、__eq__、__lt__ 和 __hash__ 是常见面试例子。对象放入集合或字典时,应同时实现 __eq__ 和 __hash__。保持一致,让值相等与哈希结果互相匹配。
使用 try、except、else 和 finally 可预测地处理失败。else 只在没有异常时运行,finally 始终用于清理。应捕获具体异常,而不是使用裸 except:,以免隐藏问题。
用 raise ValueError("...") 抛出带清晰信息的异常,或通过继承 Exception 定义自定义异常。自定义类型让调用方可以精确捕获你的领域错误。类似 class PaymentError(Exception) 的简短层级通常就够用。
类练习:在 __init__ 中赋值实例属性,显式使用 self,并区分类属性和实例属性。继承用 super().__init__(),并捕获具体异常。
示例
你的方案需要从远程 API 加载用户资料,并在网络失败时重试。你定义自定义异常子类 RetryableError,并在 try/except 块中包装调用以记录失败。你解释说,明确的异常类型能让类的契约更清晰、也更容易测试。
示例:class Dog(Animal): def __init__(self, name): super().__init__(); self.name = name
课程 5 算法、复杂度与面试策略
每个算法都先估算时间复杂度和空间复杂度。说出 O(n)、O(n log n) 或 O(n^2) 等大 O 推理,并联系输入规模。这会让面试官明白你在写代码前已经理解成本。
双指针能以 O(n) 时间和 O(1) 额外空间解决许多有序数组和回文问题。根据比较结果移动一个或两个指针,例如 left += 1 或 right -= 1。要能识别哪些暴力循环可以替换成这种模式。
使用字典或集合进行哈希,可以把重复查找从 O(n) 降到 O(1),滑动窗口则维护一段有效范围内的元素。用 collections.Counter 跟踪窗口内的频率。这两个模式在面试题中非常常见。
排序提供强力的前置条件:有序输入让二分查找、贪心选择和更简单的合并成为可能。递归天然适合分治和树问题,但一定要指出基线条件。只有深度或性能要求时,才把递归改成显式栈。
边读代码边用具体例子追踪执行过程,尤其注意改变状态的行。测试空输入、单元素、重复值、负数和大数等边界情况。先在纸上运行几个小例子,会让最终代码更有把握。
使用可复用的解决-讲解-优化流程:重述问题,提出暴力解法,再改进并解释取舍。代码写完后走一遍追踪、说明复杂度,并在有明确证据时优化。这个结构让你保持冷静,也让思路可见。
复杂度练习:写代码前先估算时间和空间。练习双指针、滑动窗口、哈希表和递归。说明权衡,并测试空输入和重复值等边界。
示例
面对两个已排序数组,你先说明暴力合并是 O(n log n),而双指针合并可以达到 O(n)。你在编码前梳理循环不变量,这体现了结构化的算法思维。随后你向面试官总结时间复杂度与代码简洁性之间的取舍。
示例:双指针可以用 O(n) 时间和 O(1) 空间判断回文。