资讯动态

Python面试核心考点:可变对象、装饰器与垃圾回收机制

发布时间:2026/10/23 23:50:40 来源:尧图企业网站定制
简介面向Python技术面试的基础知识整理属于Python开发学习教程类资源无论对于应届毕业生还是转型工程师都能帮助建立清晰的知识框架适合在考前集中复习。压缩包大小约33.16MB便于离线下载后按模块逐项查阅目前已有508人学习。其中梳理了十大核心知识域包括Python语法与类型、列表/字典/集合等数据结构、冒泡与快速排序等算法、类和继承等面向对象特性、异常处理、文件操作、正则与常用标准库、socket网络编程、SQLite数据库操作以及项目实践要点对于基础知识常见考点均有覆盖。内容以要点式总结为特色结合面试常见问题帮助读者快速回顾和查漏补缺可有效支撑编程题练习与真实面试应答是一份实用的考前速查手册能为顺利通过技术面试打下坚实基础。1. 从一份“面试知识清单”说起Python 技术面试到底在考什么很多人以为 Python 技术面试靠刷题就能过关真正把候选人分开的往往是一些日常开发里被 IDE 补全和框架封装掩盖住的语法边界。可变对象到底能不能做字典键、装饰器套在类方法上为什么多绕了一层、GIL 锁住的究竟是什么、循环引用怎么回收——这些才是面试官反复追问的角落。这份 Python 开发学习教程类的基础知识整理把散落的语法点和对象机制串成了一张可自查的清单按优先级排好了语言模型、函数式特性、面向对象、并发与内存管理缺哪块直接补哪块。它不适合零基础的人从零学语法更适合正在准备中高级 Python 岗位面试的开发者也适合团队拿来做新人入职摸底。2. 语言地基可变性、作用域与哈希约束2.1 可变与不可变对象面试官真正想听什么面试里最容易被一句话带过去、实际却占一半分的问题是is和的区别。is比较的是对象身份也就是内存地址比较的是值是否相等。很多人在实战里不会触发问题是因为小整数和短字符串有缓存机制让is意外返回True但把变量放大到缓存范围之外立刻就翻车。a [1, 2] b a b.append(3) print(a is b) # Trueb 和 a 指向同一个列表对象 print(a [1, 2, 3]) # True值相等这里要理解的不是“输出是什么”而是“赋值到底发生了什么”。b a没有创建新对象只是把a的引用复制了一份。修改b等于修改a这就是别名问题。面试官顺着这个问题往下问通常就是“那怎么才能复制一份独立的对象”于是引出浅拷贝和深拷贝这部分我在第 5 章避坑里单独展开。再看整数缓存这个经典现象x 256 y 256 print(x is y) # TrueCPython 对小整数做了缓存 x 257 y 257 print(x is y) # False超过缓存范围CPython 默认缓存了 -5 到 256 的整数对象所以这两个is的结果不同。我一般会跟人说别背这个结果要把结论记成“永远不要用is去比较数值”数值比较统一走。面试里能主动说出这层原因比只答对结果要加分。可变与不可变的下一层考点是哈希约束。Python 里不可变对象大多可哈希所以能当字典的键、能进集合可变对象比如list、dict、set都不可哈希。一个很刁钻的考法是这样的d {} d[[1, 2]] value # TypeError: unhashable type: list原因很简单字典靠哈希值定位存储位置如果键可变哈希值变了就找不到原来的项。但要注意元组里有不可变元素时可哈希元组里塞进列表就不可哈希了。这类边界题面试官考的就是你有没有把“不可变”和“可哈希”真正关联起来。2.2 作用域与 LEGB 规则global 和 nonlocal 用得对吗作用域是 Python 面试里出现频率极高的基础题因为它能用一个简单例子同时考到编译规则和闭包意识。先看这个def outer(): count 0 def inner(): count 1 return count return inner这段代码运行时会报UnboundLocalError: local variable count referenced before assignment。原因是 Python 在编译函数体时只要发现函数内部对某个名字做了赋值就把这个名字当成局部变量count 1里的count就被提前判成了局部变量所以读取时发现还没绑定。这就是 LEGB 规则的实际表现局部Local→ 外层Enclosing→ 全局Global→ 内置Built-in。inner想去读外层count是可以的但要去修改它就必须显式声明nonlocaldef outer(): count 0 def inner(): nonlocal count count 1 return count return inner加了nonlocal之后inner里对count的操作会直接作用到外层变量上。类似的模块级变量在函数里改也要加global。我给新人的建议是写代码时尽量避免在函数内部修改外层状态能通过参数传入、返回值传出的就不要依赖nonlocal和global测试和排错都会省力很多。2.3 容器类型字典与集合的哈希约束面试里容器类型的考法有两类一类是了解各自的时间复杂度另一类是掌握它们对元素类型的约束。列表和字典的增删查改时间复杂度要背熟列表按索引访问是 O(1)但按值查找是 O(n)字典查找在平均情况下是 O(1)靠的就是哈希表。集合去重是基本功但面试官会追问一句“把列表去重还能保持原来的顺序吗”。直接用set()会把顺序打乱因为集合的存储顺序取决于哈希值和插入冲突情况。想要保序去重最简单的方式是借助字典键的有序性def dedup(items): return list(dict.fromkeys(items)) nums [3, 1, 3, 2, 1, 5] print(dedup(nums)) # [3, 1, 2, 5]dict.fromkeys(items)会把列表元素作为字典的键字典从 Python 3.7 开始保证插入顺序所以这里能同时做到去重和保序。这个写法比set后再排序更通用因为排序只能处理可比较的数值而dict.fromkeys对任何可哈希对象都有效。如果面试官问“那不可哈希的对象怎么去重”可以顺势把话题引向自定义__hash__和__eq__这就是从基础题跳到对象模型题了。3. 函数进阶闭包、装饰器与延迟绑定3.1 闭包与延迟绑定陷阱闭包的定义本身不难内层函数引用了外层函数的变量并且这个内层函数被返回出去外层函数结束后这个变量仍然被内层函数持有。难的是面试官常拿循环里的 lambda 来考延迟绑定这个题的输出结果会让很多人当场懵。funcs [lambda: i for i in range(3)] for f in funcs: print(f())输出不是 0、1、2而是 2、2、2。原因是闭包捕获的不是变量当时的“值”而是变量所在的“单元”。循环结束后i停在 2所有 lambda 取到的都是同一个最终值。修复方式是用默认参数把当前值固定下来funcs [lambda ii: i for i in range(3)] for f in funcs: print(f()) # 0 1 2lambda ii: i在定义时就把i的当前值作为默认参数绑定到了函数上后续外层i怎么变都不影响。这个技巧在给回调函数传参数时特别有用比如tkinter或schedule库里的循环注册回调遇到“所有回调拿到的都是最后一个值”的问题翻回头想就是延迟绑定。3.2 带参数装饰器三层结构怎么拆装饰器的本质是“接收一个函数返回一个新函数”。不带参数的装饰器是两层外层接收函数内层包装。带参数的装饰器是三层最外层先接收装饰器参数返回一个真正的装饰器。听起来绕拆开看代码就清楚了。import functools def repeat(times2): def decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): results [] for _ in range(times): results.append(func(*args, **kwargs)) return results return wrapper return decorator repeat(times3) def greet(name): 向某人打招呼 return fHello, {name} print(greet(张三)) # [Hello, 张三, Hello, 张三, Hello, 张三] print(greet.__name__) # greet这里的repeat(times3)先执行返回decoratorPython 再把greet传给decorator得到wrapper。times参数在闭包链里被传递了三层最终在内层循环里决定执行次数。需要注意functools.wraps(func)这行它把原始函数的__name__、__doc__复制到wrapper上。如果不加greet.__name__会变成wrapper很多日志系统和 IDE 调试就会显示错误的名字。参数说明times控制函数执行次数*args和**kwargs把原函数的所有参数透传进去。这个模式可以扩展成重试装饰器比如网络请求失败自动重试times次每次间隔递增代码结构完全一样只是wrapper里加个异常捕获。3.3 用装饰器做缓存复刻 lru_cache 的核心思路缓存装饰器是面试中出现频率很高的进阶题因为它在实际项目里直接对应性能优化。Python 标准库提供了现成的functools.lru_cache面试考的是你能不能说出它的适用场景和边界。from functools import lru_cache lru_cache(maxsize128) def fib(n): if n 2: return n return fib(n - 1) fib(n - 2) print(fib(40))不加缓存时fib(40)要跑很久加了缓存后瞬间出结果因为每个n只真正计算一次其余全部命中缓存。maxsize控制缓存的最大条目数超过之后按 LRU 策略淘汰最久没用的记录。lru_cache有个硬性前提被装饰函数的参数必须是可哈希的否则会报TypeError。所以如果参数是字典或列表要么先转成元组要么自己写一个基于 id 的缓存但那种做法要自己处理内存泄漏我一般不建议在生产环境里用。手写一个简化版缓存是面试里常见的白板题核心就是用一个字典保存(参数元组 - 结果)的映射def cache(func): store {} functools.wraps(func) def wrapper(*args): if args not in store: store[args] func(*args) return store[args] return wrapper这个简化版没有淘汰策略store会无限增长。真正要落地还得加容量上限和淘汰时间这就引出了缓存设计的三个参数容量、过期时间、淘汰策略。能把这三个点自行展开说清楚比单纯背出lru_cache的用法更有说服力。4. 面向对象机制从 new 到 MRO 再到垃圾回收4.1 初始化魔法单例模式的正确写法__new__和__init__的区别是面向对象部分最直接的考点。__new__是构造方法负责创建并返回对象实例在__init__之前执行__init__是初始化方法负责给实例设置属性。日常开发几乎不用重写__new__但单例模式必须用它。class Singleton: _instance None _init_done False def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance super().__new__(cls) return cls._instance def __init__(self, name): if not self._init_done: self.name name self._init_done True s1 Singleton(A) s2 Singleton(B) print(s1 is s2) # True print(s1.name) # A第二次 init 被跳过这里的逻辑是__new__里判断类变量_instance是否已存在不存在才真正创建存在就直接返回旧实例。但只重写__new__会有个隐藏坑每次调用Singleton(B)都会执行__init__把name覆盖成B。所以额外加一个_init_done标记保证初始化只执行一次。这个处理是面试里的加分点因为很多人写了单例却说不清这个坑。4.2 super() 与 MROC3 线性化怎么排多继承是 Python 面试的深水区面试官爱考的不是“能不能多继承”而是“多个父类方法冲突时按什么顺序解析”。Python 用 C3 线性化算法计算方法解析顺序结果是每个类只出现一次且保证了单调性。看这个经典菱形继承class A: def hello(self): print(A) class B(A): def hello(self): print(B) super().hello() class C(A): def hello(self): print(C) super().hello() class D(B, C): def hello(self): print(D) super().hello() d D() d.hello()输出顺序是 D、B、C、A。用D.__mro__可以验证D - B - C - A - object。super()并不是“调用父类方法”而是“按 MRO 顺序调用下一个类的方法”。所以B里的super().hello()调用的是C的方法而不是A的这跟很多人直觉上“super 就是找父类”完全不同。理解这一点后在复杂继承体系里写super()才不容易乱。面试里遇到这个题我一般建议先把继承关系画成一棵树再按“深度优先、从左到右、子类优先”的口诀推一遍。C3 算法的细节不需要手算但要知道它保证子类始终在父类之前、多重继承时保持基类顺序。能答到这个粒度已经超过大多数候选人。4.3 垃圾回收引用计数与循环引用Python 内存管理的基础是引用计数每个对象维护一个引用计数归零立即回收。但引用计数有个拿手好戏解决不了的问题——循环引用。两个对象互相引用外部引用全部删除后它们的引用计数都不会归零于是只能靠辅助的垃圾回收器处理。import gc class Node: def __init__(self): self.child None self.parent None a Node() b Node() a.child b b.parent a del a, b print(gc.collect()) # 回收掉循环引用对象gc.collect()可以手动触发一次完整回收返回被回收的对象数量。生产代码里一般不需要主动调用但面试里要能说出Python 的垃圾回收是引用计数为主、分代回收为辅分代回收专门处理循环引用对象被分为三代越老的对象回收频率越低。有一个细节值得答出来weakref模块可以创建不增加引用计数的弱引用专门用来打破循环引用。比如缓存场景里如果缓存的值又引用回键形成一个环可以让缓存这一侧的引用用weakref这样对象能被正常回收。这个补充能体现你真正处理过内存问题而不只是背概念。5. 高频翻车点避坑指南现象背后的原因5.1 可变默认参数谁动了我的列表现象一个函数定义了默认参数为[]连续调用两次第二次调用时列表里残留了第一次的数据结果莫名叠加。原因默认参数在函数定义时就被创建成对象之后每次调用都复用同一个列表对象。函数体内的.append()修改的是这个共享对象所以第二次调用看到的是同一个列表。解决默认参数用None做哨兵函数体内再初始化。def append_to(elem, targetNone): if target is None: target [] target.append(elem) return target从这以后我写任何函数只要默认参数是容器类型一律先写None再判空。这个习惯帮我挡掉了不少隐蔽的 bug。5.2 浅拷贝翻车嵌套列表的修改地狱现象用copy.copy()复制了外层列表修改复制品的某个子列表元素结果原列表也跟着变了。原因浅拷贝只复制最外层容器内层子列表仍是同一个对象引用。解决需要完全独立的对象树时用copy.deepcopy()。但如果对象里有文件句柄、数据库连接这类不能深度复制的资源就要重写__deepcopy__或手动控制复制范围。import copy matrix [[1, 2], [3, 4]] shallow copy.copy(matrix) shallow[0][0] 99 print(matrix[0][0]) # 99原对象被改了 deep copy.deepcopy(matrix) deep[0][0] 88 print(matrix[0][0]) # 99原对象不受影响这个坑在配置管理里尤其常见比如从一份基础配置复制多份实例配置浅拷贝后所有实例共享同一个嵌套字典改一个全部遭殃。5.3 浮点数精度0.1 加 0.2 不等于 0.3现象0.1 0.2 0.3返回False判断金额是否相等时经常踩雷。原因二进制浮点数无法精确表示十进制的 0.1存储的是一个近似值两个近似值相加后误差累积。解决涉及金额计算用decimal.Decimal涉及误差容忍的比较用math.isclose()。import math print(0.1 0.2 0.3) # False print(math.isclose(0.1 0.2, 0.3, rel_tol1e-9)) # Truerel_tol是相对容差表示允许的相对误差比例。工程判断里我一般用rel_tol1e-9如果对绝对误差敏感再加abs_tol参数。这个细节答出来能直接证明你有浮点运算的实际经验。5.4 循环导入与命名空间污染现象项目变大后两个模块互相import启动时抛ImportError排查半天才发现是循环依赖。原因模块 A 导入模块 BB 在顶层又导入 A此时 A 还没执行完属性还不存在。解决把导入语句挪到函数内部延迟加载或者重新设计模块边界把公共依赖抽到第三个模块。面试里提到“延迟导入”就够了但能补充“在函数内导入”会让面试官觉得你见过真实项目结构。6. 面试自查一道综合题加一套两小时复习流程最后分享一个我常用的自查方法拿一道综合题在纸上写不借助 IDE 和搜索引擎写完再对着清单逐条验证。这道题覆盖面足够广很适合作为面试前的自测基准。题目写一个函数接收一段英文文本返回出现频率最高的 10 个单词忽略大小写和标点符号。from collections import Counter import re def top_words(text, n10): words re.findall(r[a-zA-Z], text.lower()) freq Counter(words) return freq.most_common(n) sample_text Python is powerful. Python is simple. Python is fun! print(top_words(sample_text))这道题串起了五个考察点正则表达式提取、字符串大小写归一化、Counter的用法、most_common的参数含义、以及如果要求不用Counter改用defaultdict手写统计的能力。写完以后再追问自己三个变体文本里有数字怎么办、文件特别大怎么用迭代器逐行处理、倒序输出怎么改参数。能把这套变体答顺说明这五个点真打通了。我建议的复习流程很朴素前四十分钟按本章顺序默写一遍关键结论可变与不可变、LEGB、闭包、装饰器三层结构、MRO 顺序、垃圾回收机制能默写出来才算真正记住中间四十分钟做三道手写题就按上面的综合题难度来最后四十分钟对照资源清单查漏重点看错题涉及的那个章节不要重新泛读。这套流程我从第一次准备技术面试时就开始用后来每次带新人做入职前的摸底也按同样的节奏来。从那以后我每次面试前都会强制走一遍“先默写、再手写、后查漏”的顺序基本没有在基础知识环节翻过车。希望你也能在这份知识整理里找到自己的节奏把零散的知识点变成真正能拿出手的体系祝顺利。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑