Python 面试题库

收录真实面试常见问题,答案长度适合口头表达;拿不准时可返回相关主题复习。

79个问题 初级 高级
全部难度 初级 中级 高级
逐题查看 显示全部答案
报告错误

基础

21个问题
01 Python 的赋值会做什么,为什么通过一个名称修改对象后,另一个名称也能看到变化? 初级 常见 查看答案 ▾ 收起 ▴

赋值会把名称绑定到对象;它既不会复制对象,也不是把对象装进“变量盒子”。因此,两个名称可以引用同一个可变对象。执行 backup = records 后,records.append(item) 修改的是共享列表,backup 也会看到新增项。records = [] 则不同,它只把 records 重新绑定到新列表。需要独立状态时应显式选择浅拷贝或深拷贝,并根据 API 是否要保留嵌套对象共享关系来决定拷贝深度。

延伸阅读 Python 基础
讲清楚了吗?
02 Python 循环的 else 子句何时执行,它适合解决什么问题? 初级 偶尔 查看答案 ▾ 收起 ▴

循环正常结束时才会执行 elsefor 耗尽迭代器,或 while 条件变为假。属于该循环的 break 会跳过 elsecontinue 不会。它很适合表达搜索:找到匹配记录时 break,把“未找到”路径放进 elsereturn 或未处理异常会直接离开外层函数或语句组,因此也不会到达 else。嵌套循环中,只有与这个 else 属于同一层循环的 break 才会抑制它。

延伸阅读 控制流
讲清楚了吗?
03 为什么可变默认实参会在多次调用之间共享,应该怎样修复? 初级 常见 查看答案 ▾ 收起 ▴

默认表达式在 def 语句执行时求值,所得对象保存在该函数对象上;省略对应实参的调用会重复使用它。因此,def add(item, items=[]) 会在多次调用之间累积数据,因为每次修改的都是同一个列表。通常应使用不可变哨兵值,常见的是 None,再在函数体内创建新列表。若确实需要共享状态,应给它明确的名称和所有者。嵌套 def 只有在再次执行并创建新函数对象时,才会得到一组新的默认值。

延伸阅读 函数
讲清楚了吗?
04 Python 中的相等性与对象身份有什么区别? 初级 常见 查看答案 ▾ 收起 ▴

== 通过相等性协议判断两个对象表示的值是否相等;is 判断两个引用是否指向完全同一个对象。对于 None 这类单例哨兵值,应使用身份判断,因此通常写 value is None。不要因为小例子中的字符串或整数偶尔复用了实现对象,就用 is 比较它们;驻留和缓存不是值语义契约。自定义类还能实现 __eq__,使不同实例比较为相等。处理容器或领域值时,应根据真正要问的是“同值”还是“同一对象”选择操作。

讲清楚了吗?
05 为什么集合元素必须可哈希,自定义对象需要满足什么契约? 中级 常见 查看答案 ▾ 收起 ▴

集合先用元素的哈希值确定查找位置,再用相等性确认匹配,因此元素存放期间必须保持稳定的哈希。规则并不只是“必须不可变”:任何可哈希对象都能作为成员,而元组只有在全部元素都可哈希时才可哈希。自定义对象若比较相等,就必须产生相同哈希值。插入后再修改参与 __hash____eq__ 的字段,可能让元素看起来“丢失”。集合成员宜使用不可变值对象;若记录必须可变,可改为存放稳定标识符。

延伸阅读 集合
讲清楚了吗?
06 元组的不可变性保证了什么,元组在什么情况下可哈希? 初级 常见 查看答案 ▾ 收起 ▴

元组不可变,指的是创建后不能替换、插入或删除其中的对象引用;它不会递归冻结所引用的对象。若元组中包含列表,该列表仍可修改,持有元组的代码也会看到内容变化。只有全部元素都可哈希时,元组本身才可哈希,因此包含列表的元组不能作为字典键或集合成员。元组适合固定位置的记录和多返回值;如果字段含义只能靠记住数字位置来理解,应改用具名记录类型。

延伸阅读 元组
讲清楚了吗?
45 为什么 match case 中的裸名称与常量行为不同? 中级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 的结构化模式匹配中,case READY: 这样的裸名称通常是捕获模式:它会绑定 subject,而不是与已有的局部或全局值比较。匹配所有输入的捕获还可能让后续 case 不可达。需要按值匹配时,应使用字面量或 case Status.READY: 这样的点号名称,并把具体模式放在一般模式之前,同时处理未知输入。guard 只在模式成功后执行,因此 guard 的副作用和 case 顺序都是可观察的取舍,测试必须覆盖。

延伸阅读 控制流
讲清楚了吗?
46 在 Python 中遍历时,怎样安全地删除元素? 初级 常见 查看答案 ▾ 收起 ▴

Python 3.14 并没有一条适用于所有容器的“遍历时修改”规则。改变 dictionary 或 set 的大小通常会抛出 RuntimeError;从 list 删除元素却可能继续运行,只因索引移动而漏掉相邻项。过滤时应优先使用推导式或新结果。确实需要原地删除 list 时,可遍历 items.copy(),或采用经过仔细审查的逆序索引循环;set 则先算出待删项或遍历副本。测试要覆盖相邻项都应删除、无需删除和全部删除,不能只断言没有异常。

延伸阅读 控制流
讲清楚了吗?
52 Python 函数何时能自行处理参数绑定错误? 中级 常见 查看答案 ▾ 收起 ▴

对 Python 3.14 的用户定义函数,解释器会先求值 callable 与参数表达式,再按签名绑定值,最后才进入函数体。缺少必需参数、同一参数得到多个值、意外关键字,以及违反 positional-only 或 keyword-only 规则,都会在函数体日志和内部 try 执行前抛出 TypeError。恢复逻辑必须放在调用方或转发 wrapper 中;适配器需要提前校验时,可用 inspect.Signature.bind()。一个具体风险是,后续绑定虽然失败,前面参数表达式的副作用却可能已经发生,因此应避免带副作用的调用构造。

延伸阅读 函数
讲清楚了吗?
53 函数契约测试应断言什么,而不是绑定实现细节? 初级 偶尔 查看答案 ▾ 收起 ▴

在 Python 3.14 中,调用方能观察到的是允许的参数形状、返回值或抛出的异常,以及文档声明的副作用。测试应覆盖正常输入、空值或阈值等精确边界,以及边界两侧的失败,并检查调用方拥有的对象是否被修改。除非执行顺序本身就是公开行为,否则不要依赖局部变量名或辅助函数调用顺序。类型注解仍只是契约描述,不会自动做运行时校验,因此反序列化后的错误输入需要显式边界测试。时钟、随机数、文件和网络若会隐藏关键输入,应通过参数注入。

延伸阅读 函数
讲清楚了吗?
58 list comprehension 使用什么作用域,其中创建的回调会怎样? 中级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,comprehension 的迭代目标位于隐式作用域内,因此 [item * 2 for item in values] 不会覆盖外层 item。但在该作用域中创建的函数仍然捕获绑定,而不是每次迭代的快照。若推导式生成 callback,稍后执行时它们可能都看到最终迭代值。可以用默认参数绑定当次值,或调用辅助工厂。直接计算普通值的推导式没有这种延迟执行间隔。测试应在推导式结束后调用 callback,并至少使用两个不同输入。

延伸阅读 列表推导式
讲清楚了吗?
59 怎样理解嵌套 list comprehension 的求值顺序? 初级 常见 查看答案 ▾ 收起 ▴

Python 3.14 按从左到右的嵌套顺序执行 comprehension 中的 forif,与等价的多层循环一致。最左侧 iterable 先取得;后面的子句可以使用前面绑定的名称,所有过滤条件通过后才求值结果表达式。遇到复杂推导式,应先改写成显式嵌套循环,检查名称可见性、输出顺序和调用次数。在 filter 与结果中重复调用昂贵 parser 会执行两次。若避免重复工作比保留单行更重要,应使用普通循环、辅助函数或清晰的赋值表达式。

延伸阅读 列表推导式
讲清楚了吗?
60 什么时候不应该使用 list comprehension? 中级 偶尔 查看答案 ▾ 收起 ▴

在 Python 3.14 中,list comprehension 会立即构建结果,适合把遍历、过滤和转换组合成一个清晰的值表达式。不要主要用它执行文件写入、网络调用、日志或修改操作,再把生成的 list 丢弃。若中途表达式失败,结果名称不会被赋值,但此前元素造成的外部副作用不会回滚。普通循环能更清楚地暴露部分完成、错误处理和恢复。若结果很大且只消费一次,可用 generator expression 减少物化,但这会把单次消费责任交给调用方。

延伸阅读 列表推导式
讲清楚了吗?
67 Python 类型注解会在运行时校验值吗? 初级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,annotation 提供机器可读的契约元数据,但普通赋值和函数调用不会自动强制执行。标注为 int 的 JSON 字段仍可能收到字符串、Boolean 或缺失值,除非边界代码或框架明确进行校验。静态检查用于发现开发期类型不一致,不可信的运行时数据则需要显式解析。还要注意 boolint 的子类,因此 isinstance(value, int) 会接受标志值,除非额外排除。测试应覆盖错误容器形状、字段缺失、假值和数值边界。

延伸阅读 Python 基础
讲清楚了吗?
68 Python 函数接收对象时,修改对象与重新绑定有什么区别? 中级 偶尔 查看答案 ▾ 收起 ▴

在 Python 3.14 中,函数调用会把参数名称绑定到参数表达式产生的对象,不会深拷贝对象。items.append(x) 修改的是 list 本身,因此所有 alias 都能观察到变化;items = new_list 只会重新绑定函数的局部名称,不会让调用方名称改指向新对象。无论有没有类型注解,赋值语义都一样。API 应明确选择“返回转换结果”还是“原地更新”,并同时测试 identity 与内容。浅拷贝只隔离外层容器,嵌套可变值仍可能共享。

延伸阅读 Python 基础
讲清楚了吗?
71 Python set 应怎样跨越有序 API 或序列化边界? 初级 常见 查看答案 ▾ 收起 ▴

Python 3.14 的 set 表示无序且唯一的成员关系,不承诺迭代顺序。JSON 没有原生 set 类型;若没有排序规则就直接转成 list,会产生不稳定的快照和签名。可以对可比较元素排序、按文档规定的业务 key 排序,或遍历原序列并用独立 seen set 保留首次出现顺序。必须有意选择,因为直接转 set 还会丢掉计数和原位置。若重复本身代表非法输入,应先校验再去重。测试要改变输入顺序,并断言精确外部表示,而不是某次进程中观察到的顺序。

延伸阅读 集合
讲清楚了吗?
72 API 应直接返回内部 set、副本,还是 frozenset? 中级 偶尔 查看答案 ▾ 收起 ▴

在 Python 3.14 中,直接返回内部可变 set,等于允许调用方对所有者使用的同一个对象执行 clear、添加或更新。需要可变的顶层快照时返回 set(internal);只应暴露只读且可哈希的成员关系时返回 frozenset(internal)。两者都不会递归复制或冻结成员对象,而成员本身必须可哈希且稳定。复制的时间与内存成本也与成员数量成正比,高吞吐 API 可改为提供查询方法或明确的不可变 view 契约。应通过两个调用方的修改测试验证承诺的隔离。

延伸阅读 集合
讲清楚了吗?
76 什么语法会创建单元素 tuple,为什么 tuple(value) 不同? 初级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,真正创建 tuple 的是逗号,圆括号通常只是表达式分组。(42) 是 integer,(42,) 才是单元素 tuple。tuple(value) 则把 value 当作 iterable 消费,因此 tuple("ab") 会得到两个字符串,单次 iterator 也会被耗尽。若要把一个现有对象包成单元素 tuple,应写 (value,),而不是 tuple(value)。这个区别可能静默改变字符串和 generator,而非立即报错。测试应包含空字符串、多字符字符串、已有 tuple 和单次 iterator,同时暴露形状与消费行为。

延伸阅读 元组
讲清楚了吗?
77 对于持续演进的公共 API,什么时候 tuple 不是合适的返回类型? 中级 偶尔 查看答案 ▾ 收起 ▴

在 Python 3.14 中,用 tuple 返回数据会让元素数量、顺序和含义都成为调用方契约。某个分支或后续版本增删位置时,固定长度解包会抛出 ValueError;字段增多后,裸索引也很难审查。局部使用的短 pair 仍可能清楚,持续演进的 record 通常更适合 NamedTuple、data class 或其他具名字段对象。NamedTuple 保留 tuple 行为,但其 annotation 不会校验运行时输入。应测试每个返回分支都有同一稳定形状,并把新增字段视为兼容性变更。

延伸阅读 元组
讲清楚了吗?
78 当零是有效数据时,为什么 value or default 可能出错? 初级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,or 会在第一个操作数为 truthy 时返回它,否则返回第二个;它并不是专门处理 None 的合并运算符。因此,0False"" 和空容器等有效值都会被替换为 default。只有缺失才应触发回退时,要显式检查 value is None;若 None 也是有效值,则使用唯一 sentinel。自定义真值测试可能调用 __bool__()__len__(),甚至可能抛出异常,所以不能把它当作无成本校验。测试必须覆盖每一种允许的假值。

讲清楚了吗?
79 API 在把文本或 float 转为 int 前必须决定什么? 中级 偶尔 查看答案 ▾ 收起 ▴

Python 3.14 的 int() 会随输入类型体现不同契约:int("101", 2) 按指定进制解析文本,int(3.9) 则把数值朝零截断;int("3.9") 会抛出 ValueError。这些操作都不能证明业务范围、单位或精度策略。边界代码必须规定允许的语法、符号、进制、空白、最小值和最大值,并报告非法数据,而不是静默替换默认值。金额通常不应使用二进制 float 表示精确值,应采用整数最小货币单位,或在明确舍入策略下使用 Decimal

讲清楚了吗?

函数进阶

28个问题
07 仅限位置形参与仅限关键字形参如何让 Python API 更安全? 中级 常见 查看答案 ▾ 收起 ▴

/ 之前的形参仅限位置传入;裸 **args 之后的形参仅限关键字传入。仅限位置形参不会把内部名称变成公开调用契约,因此实现可以改名而不破坏关键字调用方。仅限关键字形参会让布尔选项、单位和超时等易混值在调用处显式出现,并防止顺序写反。*args 把额外位置实参收集为元组,**kwargs 把额外关键字实参收集为新字典。只有 API 确实接受开放参数集合时,才应使用兜底形参。

讲清楚了吗?
08 为什么在循环中创建的回调常常会读到循环变量的最终值? 中级 常见 查看答案 ▾ 收起 ▴

闭包通常保留的是对外层绑定的访问,而不是函数创建时的值快照。同一循环中创建的多个回调可能共享循环变量的单元格(cell),因此稍后执行时都会读取它的最终绑定。例如,三个 lambda: queue_name 可能都返回最后一个队列名。可以写成 lambda queue_name=queue_name: queue_name 保存当前对象,或每轮调用一次辅助工厂。默认值保存的仍是对象引用;若不能看到对象后续的修改,还需要显式复制。

延伸阅读 闭包
讲清楚了吗?
09 装饰器会替换什么,为什么包装函数应使用 functools.wraps? 中级 常见 查看答案 ▾ 收起 ▴

装饰器在被装饰的定义执行时运行,并把函数名称重新绑定到装饰器的返回值。包装器可以改变调用行为,也可能遮蔽元数据和原始签名。functools.wraps(original) 会复制 __name____doc__、注解等常用属性,并设置 __wrapped__,让 inspect.signature 等工具可以沿包装链检查原函数。它并不能证明包装器正确转发参数或保持语义。透明包装器还应正确传递 *args**kwargs、返回结果、保留异常,并在包装异步函数时正确使用 await

延伸阅读 装饰器
讲清楚了吗?
10 生成器与可重复使用的集合有什么区别? 初级 常见 查看答案 ▾ 收起 ▴

生成器是一种惰性计算值的迭代器,会在多次 yield 之间保留暂停的执行帧。创建生成器不会把函数体执行到底;每次 next() 都从上一个 yield 处恢复,直到函数返回并触发 StopIteration。它只能单次遍历,耗尽后再次循环不会产生任何值。惰性可以节省内存并支持流式处理,但异常和副作用也会在消费时而非创建时发生。多个使用方需要独立遍历时,应分别创建生成器;数据规模和生命周期允许时,也可显式物化。

讲清楚了吗?
11 什么情况下 functools.partial 比 lambda 更清晰,它会保存什么? 中级 偶尔 查看答案 ▾ 收起 ▴

functools.partial 会创建一个可调用对象,调用原可调用对象时自动补上预先绑定的位置或关键字实参。当“预先固定参数”就是全部目的时,它比 lambda 更清晰,例如把 power(base, exponent) 专门化为固定 exponent=2 的平方函数。partial 对象保存的是原可调用对象和已绑定实参对象的引用,不会复制可变值。后续调用负责补齐其余参数,也可能覆盖先前绑定的关键字。若逻辑还包含分支、验证、清理或需要独立的领域接口,应使用具名函数。

讲清楚了吗?
12 即使存在同名全局名称,函数为什么仍可能抛出 UnboundLocalError? 高级 常见 查看答案 ▾ 收起 ▴

Python 会在函数运行前对整个代码块中的名称进行分类。函数内任意位置出现赋值、导入、循环目标等绑定操作,通常都会让目标名称在整个函数中被视为局部名称。于是,前面的读取会查找这个局部绑定,而不会回退到同名全局绑定;若执行到此处时局部值尚未赋予,就会抛出 UnboundLocalError。修复时应先确定状态所有权,不要盲目添加 global。优先通过参数传入并返回新值;只有最近的外层函数确实拥有持久状态时才使用 nonlocal

讲清楚了吗?
40 *args 和 **kwargs 会复制调用方传入的对象吗? 中级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,可变位置参数会得到一个新 tuple,可变关键字参数会得到一个新 dictionary,但其中的元素和值仍是调用方提供的同一批对象。在局部 kwargs 中替换顶层条目不会改变被解包的源 mapping,修改嵌套 dictionary 却可能影响调用方状态。应把参数收集理解为浅层容器边界,而不是深拷贝。需要明确所有权,只复制必须隔离的嵌套层级,并分别通过修改测试检查外层 mapping 与嵌套值。

讲清楚了吗?
41 怎样诊断 Python 转发调用中的重复关键字错误? 高级 偶尔 查看答案 ▾ 收起 ▴

Python 3.14 会按源码顺序求值参数表达式,再在进入函数体前把它们绑定到目标签名。显式关键字与 **options、两个解包 mapping,或位置参数与关键字若填充同一参数,绑定会抛出 TypeError;被调函数内部无法捕获这种进入前错误。可用 inspect.signature() 查看目标,并在转发层通过 Signature.bind() 重现契约,然后明确选择拒绝、默认或覆盖策略。函数调用会拒绝重复关键字,不能照搬 dictionary 展开时后值覆盖前值的规则。

讲清楚了吗?
42 __exit__ 如何决定 with 代码块中的异常是否继续传播? 中级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,受保护代码块失败时,__exit__ 会收到异常类型、异常值和 traceback;正常结束时三者都是 None。返回真值会吞掉该异常,返回 FalseNone 则在清理后继续传播。因此,抑制逻辑只能匹配契约明确允许的异常类型,不能无条件返回 True。还要规定清理本身失败时的优先级,因为新错误可能取代代码块错误,除非通过异常链保留两者。测试应覆盖成功、可抑制错误、无关错误和清理错误。

讲清楚了吗?
43 上下文管理器在 __enter__ 中途失败时,谁负责清理? 高级 常见 查看答案 ▾ 收起 ▴

Python 3.14 只有在 __enter__ 成功后才会调用 __exit__。如果进入阶段先取得一个资源,获取下一个资源时失败,管理器的进入逻辑必须自行释放已有的部分状态。稳健的多步骤管理器可以把每次成功获取立即登记到内部 ExitStack,全部完成后再用 pop_all() 转移所有权。不能先批量获取,最后才登记清理。故障注入测试应让第二个或更后的获取步骤失败,并断言此前每个资源都恰好释放一次。

讲清楚了吗?
44 用 contextmanager 装饰的函数必须遵守什么契约? 中级 偶尔 查看答案 ▾ 收起 ▴

在 Python 3.14 中,contextlib.contextmanager 会把恰好 yield 一次的 generator 适配为上下文管理器。yield 前的代码负责进入,产出的值成为 as 目标,恢复执行后的代码处理退出。无条件释放应放进 finally;回滚、提交或选择性抑制则通过明确的 exceptelseraise 路径表达。调用被装饰函数只会创建一次性管理器,不会立刻执行函数体,因此每次使用都应创建新实例。测试不能只走成功路径,还要覆盖代码块异常和 yield 后失败。

讲清楚了吗?
50 多层 Python 装饰器按什么顺序求值、应用和调用? 中级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,def 上方的 decorator 表达式先从上到下求值,得到的 callable 再从下到上应用到函数。因此,@outer 位于 @inner 上方时,最终绑定等价于 outer(inner(func))。实际调用时先进入外层 wrapper,最后离开外层。这个区别会影响鉴权、重试、缓存、事务和异常边界。不要只凭缩进猜顺序,应展开嵌套调用,并用事件记录测试进入、结果、异常和退出,还要覆盖 cache hit 与调用被拒绝的路径。

延伸阅读 装饰器
讲清楚了吗?
51 为什么同步 wrapper 会破坏 async 函数装饰器的执行边界? 高级 偶尔 查看答案 ▾ 收起 ▴

在 Python 3.14 中,调用 async def 只会返回 coroutine 对象,函数体要到被 await 时才执行。同步 wrapper 若只是返回 func(*args, **kwargs),包围的只是 coroutine 创建,不是之后的实际执行,因此计时日志和 try 会在异常发生前结束。透明的异步 decorator 需要 async def wrapper,并在预期的日志、清理、重试或异常边界内执行 await func(...)wraps 能保留元数据,却不能修复行为。测试应覆盖 await 后的成功、异常和取消,重试代码也不能使用阻塞的 time.sleep()

延伸阅读 装饰器
讲清楚了吗?
54 Python 3.14 的 functools.Placeholder 解决了 partial 的什么问题? 高级 常见 查看答案 ▾ 收起 ▴

Python 3.14 之前,partial() 只能预填一段连续的前导位置参数;要固定后面的槽位,通常需要 wrapper 或关键字。Python 3.14 新增 functools.Placeholder,可以保留某个位置槽,由之后的调用填入。每个 placeholder 都必须获得一个位置参数,而且不能作为关键字 placeholder 使用。它能简洁适配位置参数 API,但也会提高最低运行时版本,并可能让调用形状更难读。需要校验、强制策略或更清楚的参数语义时,应优先写具名 wrapper。

讲清楚了吗?
55 为什么 reduce 的 initializer 是契约的一部分,而不只是便利参数? 中级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,functools.reduce(function, iterable, initial) 会把 initial 视为位于输入之前。空输入直接返回它,单元素输入也会调用一次二元函数。省略 initializer 时,首元素成为 accumulator,空输入则抛出 TypeError。因此 seed 决定结果类型和空输入语义。存在真正 identity 时才使用;没有合理 identity 时,应先拒绝空输入,而不是发明误导性默认值。dict.update() 这类原地方法返回 None,把它藏进 reducer 会让下一步收到错误的 accumulator。

讲清楚了吗?
56 为什么 generator 应使用 return,而不是抛出 StopIteration? 高级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,若 StopIteration 从 generator 函数体逃出,会被转换为 RuntimeError: generator raised StopIteration;这样能避免嵌套操作意外耗尽时静默截断输出。正常结束 generator 应使用 returnreturn value 会进入 StopIteration.value,可由 yield from 接收,但普通 for 不会把它作为元素产出。可选读取另一个 iterator 时,应使用 next(source, default),或在局部捕获 StopIteration。测试要覆盖空输入与已耗尽输入,避免把正常协议边界误判为应用失败。

讲清楚了吗?
57 跳出 for 循环会关闭 generator 拥有的资源吗? 中级 偶尔 查看答案 ▾ 收起 ▴

在 Python 3.14 中,break 只会退出循环,通常不会对 iterator 调用 close()。仍被引用的 generator 可能暂停在 yield,继续占用文件、cursor 或锁,直到恢复、显式关闭或被回收。释放逻辑要放进 generator 的 finally,拥有消费责任的一方则在自己的 finally 中调用 close(),或使用 contextlib.closing()。不能依赖立即垃圾回收,因为那是与实现相关的生命周期假设。测试应只消费一个元素就停止,并断言清理立即发生。

讲清楚了吗?
61 哪些调用会共享 lru_cache 条目,为什么等价调用也可能 miss? 中级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,lru_cache 根据调用形式构造 key,不会先把每次调用绑定并规范化为统一签名。位置与关键字写法、省略与显式提供默认值,甚至不同关键字顺序,都可能占用不同条目。设置 typed=True 还会区分直接参数的类型,但不会递归标记嵌套内容的类型。命中率重要时,应统一内部调用约定,并在代表性流量下检查 cache_info()。不能让正确性依赖“看似相等的调用一定合并”,因为文档契约之外的 key 细节属于实现问题。

讲清楚了吗?
62 并发时,lru_cache 是否保证每个 key 只计算一次? 高级 常见 查看答案 ▾ 收起 ▴

Python 3.14 会保持 lru_cache 内部 mapping 在线程并发下的一致性,但这种线程安全不等于 single-flight。两个线程可能在任一结果写入前同时看到同一个 cold key,于是被包装函数会执行多次。因此,函数必须能容忍重复执行;若重复工作或副作用不可接受,应在业务层加入按 key 协调。预热后的 cache 测试发现不了这个竞争,必须故意重叠两个冷调用。也不要直接缓存 async def,因为那会保存 coroutine 对象,而同一个 coroutine 不能安全地重复 await。

讲清楚了吗?
63 什么时候不应该给函数加 lru_cache? 中级 偶尔 查看答案 ▾ 收起 ▴

在能明确说明“完整 key 的结果有效到哪个事件”为止之前,不应缓存。Python 3.14 的 lru_cache 只能看到参数;数据库行、权限、配置、时间等隐藏依赖发生变化时,条目不会自动失效。可以把稳定版本加入 key,或在拥有数据变更的事务提交后调用 cache_clear()。key 空间无界时使用有限 maxsize,并优先缓存不可变结果,因为命中会返回同一个已存对象。缓存实例方法还会把 self 保留在 key 中,可能延长实例生命周期。应测量命中分布与对象大小,不能假定复用一定划算。

讲清楚了吗?
64 Python 3.14 的 map(strict=True) 如何改变多输入映射? 高级 常见 查看答案 ▾ 收起 ▴

多输入 map() 默认在最短 iterable 耗尽时停止,并静默忽略较长输入的尾部。Python 3.14 新增 strict=True;长度不一致时会在迭代过程中抛出 ValueError。构造仍是惰性的,因此只创建 map 既不能证明长度相同,也不能证明转换成功。测试必须真正消费,而且发现不一致前可能已经产出若干结果。strict 模式能发现对齐错误,却不会回滚消费者此前的写入。业务要求全有或全无时,要使用事务或预校验;只有明确允许截断时才保留默认行为。

讲清楚了吗?
65 为什么 filter(None, values) 可能删除有效数据? 中级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,filter(None, values) 会执行普通真值测试,只保留 truthy 元素。它删除的不只是 None,还包括 0False、空字符串和空容器;这些可能是有效业务值,例如零余额或明确允许的空标签。若只想去掉缺失值,应使用 lambda value: value is not None 这样的精确 predicate。返回结果是惰性、单次消费的 iterator,因此测试必须实际消费,并包含所有允许的假值。只有真正需要可重复使用 collection 的 API 边界才应物化。

讲清楚了吗?
66 什么时候 comprehension 或循环比 map、filter、reduce 更清楚? 中级 偶尔 查看答案 ▾ 收起 ▴

在 Python 3.14 中,map(named_function, source) 能清楚强调具名转换的复用;comprehension 则把短表达式、过滤条件和迭代结构放在一起。需要惰性时,generator expression 通常比嵌套 lambda 更直接。只有自定义 accumulator 与二元转换仍容易解释时才使用 reduce()sumanyall、字符串连接或显式循环往往更准确地命名结果。涉及副作用、部分失败策略或多项状态更新时,普通循环更合适。不要只为缩短代码选择函数式形式,并要说明惰性结果由谁消费。

讲清楚了吗?
69 哪些 Python 语句会创建局部作用域,哪些只创建 suite? 中级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,函数调用会创建局部 namespace;module、class definition、comprehension 和 annotation scope 则各有专门规则。普通 ifforwhilewithtrymatch suite 不会创建类似函数的局部作用域。其中绑定的名称通常能在当前函数后面访问,但前提是运行时确实执行了该绑定;未进入的分支因此可能导致 UnboundLocalError。不能把其他语言的 block scope 习惯直接套用。应显式初始化必要值,或组织分支,使每条继续执行的路径都完成绑定。

讲清楚了吗?
70 为什么通过 locals() 赋值不能可靠地修改函数局部变量? 高级 偶尔 查看答案 ▾ 收起 ▴

在 Python 3.14 中,locals() 会暴露表示当前局部 namespace 的 mapping,但优化后的函数 frame 并不把 mapping 写入作为修改快速局部变量的受支持接口。读取它适合诊断,或传给明确接收 mapping 的 API;先执行 locals()[name] = value,再读取裸名称 name,不能作为可靠程序逻辑。动态字段应放入显式 dictionary、data class 或普通对象。也不要把整个 mapping 直接写入日志,其中可能含有凭据或大型对象。测试应直接针对显式容器契约。

讲清楚了吗?
73 singledispatch 选择实现时检查哪个值? 中级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,functools.singledispatch 只根据第一个参数的运行时类型选择实现。后续参数、容器元素类型和返回注解都不会增加分派维度。因此,即使注册逻辑想处理 valuesave(context, value) 实际仍会按 context 分派。应把决定行为的对象放在首位,或改用更清楚的条件判断或多分派设计。singledispatchmethod 会跳过已绑定的 selfcls,使用第一个普通参数。测试应分别改变每个参数,并断言 generic.dispatch(type) 的结果。

讲清楚了吗?
74 singledispatch 如何解析子类和重叠的抽象基类? 高级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,精确注册最先获胜;否则 singledispatch 会考察具体类的 MRO 与适用的抽象基类。最近的普通已注册基类优先,但两个互不相关且都匹配的 ABC 可能产生歧义并抛出 RuntimeError,不会按注册顺序决定。内建继承关系也很重要:boolint 的子类,因此若没有更具体注册,整数 handler 会接收标志值。应建立覆盖精确类型、普通子类、虚拟子类、bool 和未注册类型的分派矩阵。测试解析要用 dispatch(ConcreteType),因为 registry[type] 只能看到显式条目。

讲清楚了吗?
75 在插件系统中使用 singledispatch 注册有什么风险? 中级 偶尔 查看答案 ▾ 收起 ▴

Python 3.14 的注册通常在模块 import 时执行,因此未进入真实启动路径的插件不会提供任何 handler。对同一精确类型再次注册会改变共享 generic function,后注册者获胜,使 import 顺序成为行为的一部分。应定义确定性的发现流程、重复冲突策略,并在启动时通过 dispatch() 检查关键具体类型。公共 API 没有 unregister 操作,因此测试应创建局部 generic function,或使用全新进程隔离。新增宽泛 ABC 注册同样是 API 变更,因为已有类可能开始选择新的实现。

讲清楚了吗?

对象与类

9个问题
13 Python 的类属性与实例属性如何相互作用? 初级 常见 查看答案 ▾ 收起 ▴

普通实例读取属性时,如果实例没有自己的值,就会通过包括类继承链在内的机制继续查找。因此,类属性可作为共享的后备值;而 account.currency = "EUR" 这类赋值通常会创建或替换实例属性。新值只在该实例上遮蔽 Account.currency,不会改变类或其他实例。可变类属性尤其容易出错:继承到的列表可能被所有实例意外共享。每个对象独有的可变状态应在 __init__ 中创建;只有确实需要共享所有权时,才通过类修改共享配置。

延伸阅读
讲清楚了吗?
14 为什么 super() 遵循 MRO,而不只是“调用父类”? 高级 偶尔 查看答案 ▾ 收起 ▴

super() 会把调用委托给当前类的方法解析顺序(MRO)中的下一个实现,并不一定是某个写死的父类。这在菱形多重继承中很重要:只要各方法协作,调用就能沿 C3 线性化顺序前进,使每个实现执行一次。所有参与方法都必须使用兼容签名,消费自己负责的参数,转发其余参数,并一致调用 super()。直接调用某个基类方法可能跳过其他类,或让共同祖先执行两次。行为不清楚时检查 Class.mro();若各类无法维持同一协作契约,应优先使用组合。

延伸阅读
讲清楚了吗?
15 抽象基类会强制什么,又不会强制什么? 中级 常见 查看答案 ▾ 收起 ▴

使用 ABC@abstractmethod 定义的抽象基类,会在子类仍有未实现抽象方法时阻止其实例化。这样,不完整的名义实现会在构造阶段失败,同时也为 isinstance 判断和共享行为提供统一目标。抽象方法本身仍可包含可复用实现,子类可以通过 super() 调用。该机制不会验证任意方法签名,也不能保证语义正确;子类完全可能把同名方法实现错。注册虚拟子类只影响成员关系判断,不会注入方法,因此测试仍须覆盖行为契约和失败路径。

延伸阅读
讲清楚了吗?
16 为什么 dataclass 的可变默认字段应使用 default_factory? 中级 常见 查看答案 ▾ 收起 ▴

field(default_factory=list) 保存的是可调用对象,并在每次创建 dataclass 实例时调用它,因此每个对象都会得到自己的列表。若直接把可变列表写成字段默认值,就会暗示所有实例共享同一对象;dataclasses 会对常见的不可哈希可变默认值抛出 ValueError。工厂也可为字典、集合或领域对象创建独立默认值。default_factory 接收的是无参数可调用对象,而不是调用结果。跨多个字段的派生值或验证逻辑应放进 __post_init__,因为它需要完整初始化后的实例。

延伸阅读
讲清楚了吗?
17 属性查找时,数据描述符与非数据描述符有什么区别? 高级 少见 查看答案 ▾ 收起 ▴

描述符是通过协议方法定制属性访问的类属性。数据描述符除 __get__ 外还定义 __set____delete__,它的优先级高于实例的 __dict__。非数据描述符只定义 __get__,因此实例属性可以遮蔽它。函数就是非数据描述符;实例访问函数时会得到绑定方法,同时仍允许实例级遮蔽。可复用描述符通常应把每个实例的值存进实例本身或弱键存储,并在 __get__ 收到 instance=None 时返回描述符自身,以支持类级访问。

延伸阅读
讲清楚了吗?
18 元类在类创建过程的哪个阶段参与,什么情况下值得使用? 高级 少见 查看答案 ▾ 收起 ▴

元类是“类的类”,通常默认使用 type。执行 class 语句时,__prepare__ 可以提供命名空间,类体向其中写入内容,随后元类的 __new____init__ 创建并初始化类对象。以后调用这个类创建实例时,会经过元类的 __call__。只有框架需要在类定义时一致地校验或注册行为,才通常值得自定义元类。局部转换应优先考虑类装饰器或 __init_subclass__,因为元类会沿继承关系传播,多个无关元类还可能发生冲突。

延伸阅读
讲清楚了吗?
47 deepcopy 如何处理对象图中的环和共享子对象? 高级 常见 查看答案 ▾ 收起 ▴

在 Python 3.14 中,copy.deepcopy() 会在一次遍历中维护从源对象 identity 到副本的 memo mapping。若源图中两条边指向同一个可变子对象,副本中的两条边通常也指向同一个新子对象;若一条边回到祖先,memo 会闭合这个环,而不是无限递归。自定义 __deepcopy__(memo) 必须先登记新对象,再递归复制子节点,并继续传递同一个 memo。朴素递归可能重复共享状态或在环上溢出。测试应检查 identity 关系和反向引用,不能只比较打印值。

讲清楚了吗?
48 Python 3.14 中的 copy、deepcopy 和 copy.replace 有什么区别? 中级 常见 查看答案 ▾ 收起 ▴

copy.copy() 会创建新的外层对象,但通常保留对子对象的引用。copy.deepcopy() 递归遍历对象图,并通过 memo 保持共享关系与环。Python 3.13 新增、3.14 继续提供的 copy.replace() 会创建同一受支持类型,并按名称替换指定字段;未改字段遵循该类型的替换语义,所以 replacement 不等于深拷贝。它支持 named tuple、data class 和定义了 __replace__() 的类。选择方式应从修改路径与所有权契约出发。盲目深拷贝可能成本很高,也可能不适用于锁、句柄和 identity 具有业务含义的对象。

讲清楚了吗?
49 怎样测试复制操作确实提供了契约承诺的独立性? 中级 偶尔 查看答案 ▾ 收起 ▴

在 Python 3.14 中,只比较 equality 无法证明副本独立,因为两个不同对象图在修改前可能值相等。测试夹具应包含可变嵌套子对象、两个字段共享同一子对象、允许时加入一个环,以及策略明确要求继续共享的对象。复制后沿结果中的各条关键路径修改,并断言源对象哪些路径应变化、哪些不应变化,同时检查 identity 和源图到副本图的拓扑。这样能区分赋值、浅拷贝、保留拓扑的深拷贝和朴素递归复制。夹具要保持有界,因为完整深拷贝可能代价很高。

讲清楚了吗?

并发

7个问题
19 协程对象与 asyncio Task 有什么区别? 初级 常见 查看答案 ▾ 收起 ▴

调用 async def 函数会创建协程对象,但不会独立调度函数体。await 该对象时,它作为当前任务的一部分被驱动。asyncio.Task 会包装协程,把它安排到正在运行的事件循环,并保存结果、异常或取消状态。因此,创建任务是一项所有权决定,并不只是 await 的另一种写法。调用方必须保留并最终等待该任务,或在 TaskGroup 中创建它,由任务组限定子任务生命周期并定义失败传播路径。

延伸阅读 asyncio
讲清楚了吗?
20 asyncio.TaskGroup 如何处理失败的子任务? 中级 常见 查看答案 ▾ 收起 ▴

TaskGroup 把所有子任务视为一项结构化操作。某个子任务抛出非取消异常后,任务组会取消尚未完成的兄弟任务,等待它们执行清理,并在退出上下文时把仍需报告的失败作为异常组抛出。协程应在 finallyasync with 中释放资源,并通常让 CancelledError 继续传播。默认的 asyncio.gather 不同:它会向等待方传播首个异常,但不会仅因该异常自动取消其他 awaitable。应根据一个任务失败后其余工作是否仍有意义来选择 API。

延伸阅读 asyncio
讲清楚了吗?
21 并发执行 Python 工作时,应如何在线程与进程之间选择? 中级 常见 查看答案 ▾ 收起 ▴

应根据工作负载和共享边界选择。线程共享同一进程和内存,适合阻塞 I/O,也适合会释放解释器锁的库;但共享可变状态必须同步。进程拥有独立内存,可以并行执行 CPU 密集型 Python 工作,代价是启动、进程间通信、序列化和更高资源消耗。常见的启用 GIL 的 CPython 构建不会让纯 Python 的 CPU 密集线程并行,但这个结论不能代替测量。应对真实任务做基准测试,把数据传输成本算进去,并覆盖关闭和失败路径。

延伸阅读
讲清楚了吗?
22 即使存在 GIL,Python 多线程代码为什么仍会发生竞态条件? 中级 常见 查看答案 ▾ 收起 ▴

GIL 并不会把应用不变量自动包成事务。检查账户余额再更新余额这类逻辑操作,包含多次读取、判断和写入;另一个线程可能在这些步骤之间穿插执行,特别是在阻塞调用或显式释放锁的边界。不要根据一次观察到的字节码序列,或某个内置操作在特定解释器构建上看似原子,就推断代码线程安全。应使用 Lock 保护完整不变量,或把状态交给单一 worker 并通过队列通信,也可使用不可变消息。测试要主动制造交错,并验证结果与关闭行为。

延伸阅读
讲清楚了吗?
23 什么情况下应使用 Executor.map,而不是 submit 配合 as_completed? 中级 偶尔 查看答案 ▾ 收起 ▴

对一组输入应用同一个可调用对象,并要求按输入顺序消费结果时,适合使用 Executor.map。因此,前面的慢任务即使不影响后续任务完成,也可能阻塞后续结果交付。每个作业需要独立元数据、取消或错误处理时,应使用返回 Futuresubmit;再迭代 as_completed(futures),就能按完成顺序立即处理快速结果。两种方式中,读取结果都会重新抛出 worker 异常。还应限制提交量,定义超时,并通过上下文管理器或显式 shutdown() 关闭执行器。

延伸阅读
讲清楚了吗?
24 为什么会重试的 Celery 任务应具备幂等性? 高级 偶尔 查看答案 ▾ 收起 ▴

Celery 任务通过 broker 交给 worker;外部副作用已经发生后、消息确认或结果记录之前,仍可能出现故障。重试或重新投递就可能再次执行该任务。任务应使用稳定的操作键,让写入具备条件性或安全的 upsert,并尽可能在与副作用相同的持久化边界记录完成状态。只对瞬时故障进行有界退避和抖动重试,永久性验证错误应立即失败。acks_late 会改变确认时机,但它本身不能让非幂等的付款或邮件发送变得安全。

延伸阅读
讲清楚了吗?
37 asyncio 协程拥有资源时,应如何处理取消? 高级 常见 查看答案 ▾ 收起 ▴

取消是一种协作式控制信号,会在暂停点以 CancelledError 的形式送达。资源释放应放在 finally 中,或交给异步上下文管理器,使操作成功、失败或取消时都能执行清理。协程可以短暂捕获取消以恢复不变量,但清理后通常应重新抛出;吞掉取消可能破坏 TaskGroup 与超时边界。清理本身需要等待时,所有权边界应持续到清理完成。只有内部操作明确不可取消时才使用 shield,同时要记住调用者仍可能被取消。

延伸阅读 asyncio
讲清楚了吗?

标准库

6个问题
25 什么情况下应选择 collections 中的 Counter、defaultdict 或 deque? 初级 常见 查看答案 ▾ 收起 ▴

应选择最能表达操作意图的容器。Counter 把可哈希值映射到计数,并提供 most_common 等频次操作。defaultdict(factory) 在通过 __getitem__ 访问缺失键时创建并插入默认值,适合分组,但一次看似读取的操作也可能修改映射。deque 能高效地在两端追加和弹出,因此实现 FIFO 队列时优于反复删除列表索引 0。这些类型不能替代领域验证;API 边界仍需定义负计数、缺失分组、队列最大长度和序列化的含义。

延伸阅读
讲清楚了吗?
26 为什么 itertools.groupby 可能为同一个键产生多个分组? 中级 偶尔 查看答案 ▾ 收起 ▴

itertools.groupby 只把键相同的连续区段分组,并不会像数据库的 GROUP BY 那样收集整个可迭代对象中的相同键。因此,按 A、B、A 排列的输入会产生三个分组。需要全局分组时,应先按同一个键排序,同时考虑排序的时间和内存成本。每个返回分组都是与外层 groupby 迭代器共享底层输入的迭代器。外层继续前进后,早先分组可能不再可用,所以应立即消费;若分组需要延长生命周期,则应显式物化。

延伸阅读
讲清楚了吗?
27 pathlib 改善了什么,哪些边界仍需显式处理? 初级 常见 查看答案 ▾ 收起 ▴

pathlib.Path 用对象表示文件系统路径,并提供 / 拼接、修改后缀、遍历和打开文件等可组合操作。相比手工拼接分隔符,它提高了可读性和跨平台性,但不会让 I/O 自动可靠或安全。相对路径仍依赖当前工作目录,resolve() 只是改变路径视图而不是授予访问权限,而且文件可能在检查后、使用前发生变化。文本操作应指定编码,处理预期的 OSError 子类,并在真正执行操作时验证用户选择的路径仍位于允许的根目录内。

延伸阅读
讲清楚了吗?
28 Python 数据经过 JSON 边界时会丢失哪些信息? 中级 常见 查看答案 ▾ 收起 ▴

JSON 承载的是小型、语言无关的数据模型,而不是任意 Python 对象的身份和行为。对象会表示为字符串键映射,数组会变成列表;自定义类、集合、字节、作为独立类型的元组以及带时区日期时间,都需要显式表示。因此,往返转换可以保留业务数据,却未必保留原始 Python 类型。应在边界定义 schema 并显式转换,不要依赖会悄悄抹掉结构的 default=str。输入必须视为不可信数据:按场景限制大小和嵌套深度,验证必需字段,并明确是否接受非标准数值。

延伸阅读
讲清楚了吗?
29 为什么一次 Python 日志调用可能产生重复输出? 中级 常见 查看答案 ▾ 收起 ▴

Logger 按点分名称形成层级。service.api 处理的记录通常还会向祖先 logger 传播,因此在子 logger 和 root 上挂载等价 handler,可能让同一记录输出两次。库代码应通过 logging.getLogger(__name__) 创建具名 logger,把 handler 策略交给应用。应用在入口处统一配置 handler、级别、格式和目标。只有子 logger 确实需要独立处理时,才应有意设置 propagate=False。还要记住 logger 与 handler 的级别都会过滤记录;需要堆栈时,应在活动异常处理块中使用带异常信息的日志调用。

延伸阅读
讲清楚了吗?
30 怎样使用 subprocess 安全地运行外部命令? 中级 常见 查看答案 ▾ 收起 ▴

对大多数有界命令,应调用 subprocess.run,并把可执行文件及每个实参分别放进列表元素。这样可以避开 shell 解析,让不可信文本始终只是一个参数。若非零退出码应视为失败,设置 check=True;同时设置超时,并明确输出是字节,还是按指定编码解码的文本。不要对用户可控数据使用 shell=True,否则转义错误会演变为命令注入。流式或长期交互可使用 Popen,但必须谨慎管理管道,避免双方死锁。需要可复现时,还应明确子进程环境和工作目录。

延伸阅读
讲清楚了吗?

类型与工具链

6个问题
31 Python 类型提示会在运行时强制类型吗? 初级 常见 查看答案 ▾ 收起 ▴

不会。类型提示为静态检查器、编辑器、审查者以及主动读取注解的库描述契约;普通函数调用不会依据注解验证实参或返回值。HTTP 请求、JSON、环境变量和数据库行等运行时边界仍需要解析与验证。Any 是绕过静态检查的出口,并不证明值安全;如果代码接受任意值,但使用前必须缩窄类型,通常 object 更合适。应把静态检查与运行时验证分开设计,并测试转换层能否清楚报告无效输入。

延伸阅读
讲清楚了吗?
32 typing.Protocol 与抽象基类有什么区别? 高级 偶尔 查看答案 ▾ 收起 ▴

Protocol 描述一个值为满足静态结构子类型所必须提供的成员。类无需继承或注册就能满足协议,这与鸭子类型 API 一致,也避免实现与某个继承层级耦合。抽象基类默认采用名义关系,可以在抽象方法未实现时阻止实例化,还能提供共享运行时行为。@runtime_checkable 允许对协议进行有限的 isinstance 检查,但这种检查只关注属性是否存在,不会验证完整签名或语义。协议应保持小而由使用方定义;跨越不可信运行时边界时仍需单独验证行为。

延伸阅读
讲清楚了吗?
33 应如何选择 pytest fixture 的作用域? 中级 常见 查看答案 ▾ 收起 ▴

应选择与资源真实所有权一致、且能保持测试隔离的最窄作用域。function 作用域为每个测试创建新 fixture,是可变状态最安全的默认选择。class、module、package 或 session 作用域可减少昂贵初始化,但任何修改都可能泄漏到其他测试,使结果依赖执行顺序。yield fixture 应在 yield 前获取资源,并在 finally 中释放,确保测试失败时也会清理。不要只为提速就扩大作用域;先测量初始化成本,显式重置共享状态,并验证断言失败后文件、事务、进程或临时服务仍会关闭。

延伸阅读
讲清楚了吗?
34 wheel 与源码分发包有什么区别? 中级 偶尔 查看答案 ▾ 收起 ▴

源码分发包(sdist)包含源材料和构建元数据,安装时通常需要在目标环境执行构建步骤。wheel 是已构建分发包,安装器可以把它直接展开到环境中,而无需运行项目常规构建;不过 wheel 可能受 Python 版本、ABI 或平台限制。若下游可能使用未覆盖平台,或需要检查并重新构建源码,通常应同时发布两者。测试对象应是实际产物,而不只是仓库工作树。还要在 pyproject.toml 中声明构建后端及其依赖,并进行隔离构建,防止本地未声明工具掩盖依赖缺失。

延伸阅读
讲清楚了吗?
35 Python 虚拟环境会隔离什么,又不会隔离什么? 初级 常见 查看答案 ▾ 收起 ▴

虚拟环境为项目提供独立的解释器配置和包安装目录,因此安装一个项目的依赖通常不会修改其他项目或基础环境中的包。激活主要调整 PATH 等 shell 变量,只是使用便利,并非必需;直接调用环境内的解释器同样能选中它。venv 不会隔离操作系统库、进程、网络访问、凭据或容器,也不是可在机器之间复制的可移植产物。应根据已声明并锁定的依赖重新创建环境,把它排除在版本控制之外,并确认 pythonpip 实际指向哪个解释器。

延伸阅读
讲清楚了吗?
36 应如何在 timeit、cProfile 与采样分析器之间选择? 中级 偶尔 查看答案 ▾ 收起 ▴

应先明确需要回答的问题。timeit 会在受控条件下重复测量一段小操作,适合在分离初始化成本后比较局部方案。cProfile 会在代表性运行中记录函数调用,并报告调用次数、累计时间和单次时间,便于定位应用把时间花在哪里,但会引入分析开销。采样分析器周期性观察调用栈,通常对线上进程干扰更小,代价是结果近似,而且对极短调用细节较少。应使用真实数据,重复测量并检查波动,只优化确实影响端到端负载的瓶颈。

延伸阅读
讲清楚了吗?

函数

2个问题
38 为什么循环中创建的 Python 闭包常看到最终值,应怎样修复? 中级 常见 查看答案 ▾ 收起 ▴

闭包保留对循环变量绑定的访问,而不是保存每次迭代的快照。因此,循环结束后调用这些闭包时,它们会读取共享 cell 中最后存入的对象。如果每个回调都需要当次值,可以用默认参数在函数创建时绑定,例如 lambda item=item: item;也可以调用辅助工厂,让每次迭代创建新的局部绑定。每个回调还拥有可变私有状态或更复杂行为时,应优先使用辅助工厂。测试要在循环结束后调用回调,并改变外层状态,避免立即调用掩盖延迟绑定。

延伸阅读 闭包
讲清楚了吗?
39 闭包何时需要 nonlocal,何时可以不使用它而修改状态? 中级 常见 查看答案 ▾ 收起 ▴

内层函数要重新绑定外层函数作用域中的名称时,必须使用 nonlocal。如果省略它,赋值会把该名称归类为局部名称;先读取再赋值时可能抛出 UnboundLocalError。通过现有绑定修改对象则不同:items.append(value) 改变列表,却没有重新绑定 items,所以不需要 nonlocal。应有意区分两种操作。重新绑定一个简单的不可变计数器可能很清楚;多个相关状态字段和不变量则更适合放进 dataclass 或字典。当显式对象能让所有权边界更清楚时,应避免使用 nonlocal

延伸阅读 闭包
讲清楚了吗?