PEP 851:为列表新增 list.discard() 方法

PEP 851:为列表新增 list.discard() 方法

💡 原文英文,约800词,阅读约需3分钟。
📝

内容提要

该提案建议为Python列表新增list.discard(x)方法:删除第一个等于x的元素,成功返回True,不存在则返回False,不抛异常。它复用remove()的查找与删除逻辑,语义与set.discard()对应,可替代“先判断再删除”或捕获ValueError的写法,避免重复扫描和线程竞争。collections.UserList同步支持;collections.abc.MutableSequence和deque暂不改动。

🔎

延伸解读

为什么需要 list.discard():消除重复扫描与异常捕获

文章指出,删除可能不存在的元素时,常见写法是“先判断再删除”或捕获 ValueError。前者在命中时会扫描列表两次,且多线程下可能因两次调用之间元素被移除而抛出异常;后者虽避免重复扫描,但每次未命中都要付出异常开销,且 except ValueError 可能掩盖 __eq__ 中的无关错误。list.discard() 将查找与删除合并为一次调用,直接表达意图,并复用 remove() 的底层逻辑,实现成本低。

与 set.discard() 的语义差异:名称相似但行为不同

文章明确说明,list.discard() 与 set.discard() 存在真实差异:对集合执行 s.discard(x) 后,x not in s 必然成立;但对列表执行 lst.discard(x) 后,若 x 出现多次,该条件不一定成立。这是因为 list.discard() 刻意沿用 list.remove() 的查找与删除语义,只删除第一个匹配项。文档也明确记载了这一点。名称沿用 set 的 remove/discard 区分,是为了让熟悉集合的用户能直接迁移知识,但需注意列表的重复元素特性。

返回值设计:bool 而非 None,便于条件判断

与 set.discard() 返回 None 不同,list.discard() 返回布尔值,表示是否删除了元素。文章解释,实现本身已知结果,返回 bool 没有额外成本,且允许调用方用 if lst.discard(x): 替代“先判断再删除”的双重扫描。这一设计由 Guido van Rossum 建议。同时,提案强调只有“无匹配项”才由返回值报告,其他错误(包括比较时抛出的 ValueError)仍会传播,避免掩盖问题。

影响范围与兼容性:暂不扩展至 ABC 和 deque

文章说明,collections.UserList 会同步获得该方法以保持一致,但 collections.abc.MutableSequence 和 collections.deque 暂不改动。不修改 ABC 是因为会改变所有第三方子类的接口,而通过 register() 注册的虚拟子类又不会自动获得该方法。deque 的支持被留作后续常规变更。此外,用 hasattr(obj, "discard") 区分集合与列表的代码可能受影响,但标准库和前 5000 个 PyPI 包中未发现此类用法。

❓

Q&A

Python 列表新增的 list.discard() 方法具体是做什么的?

list.discard(x) 会删除列表中第一个等于 x 的元素,如果存在则返回 True,不存在则返回 False,不会抛出异常。它是 list.remove() 的非抛异常版本,类似于 set.discard() 与 set.remove() 的关系。

为什么需要 list.discard()?现有写法有什么问题?

现有写法有两种:一是先判断 if x in lst: lst.remove(x),但命中时会扫描列表两次,且多线程下可能在判断和删除之间被其他线程删除导致 ValueError;二是 try: lst.remove(x) except ValueError: pass,虽然避免上述问题但意图不清晰,且每次未命中都要处理异常,还可能掩盖 __eq__ 抛出的无关错误。list.discard() 用单次调用直接表达意图,避免重复扫描和异常开销。

list.discard() 和 set.discard() 在行为上有什么不同?

主要区别有两点:1) set.discard() 返回 None,而 list.discard() 返回布尔值表示是否删除了元素;2) 对于重复元素,set.discard(x) 后 x 一定不在集合中,但 list.discard(x) 只删除第一个匹配项,如果列表中有多个 x,删除后 x 仍可能存在。list.discard() 刻意沿用 list.remove() 的查找和删除语义,而非 set.discard() 的语义。

list.discard() 会影响哪些其他类型?collections.abc.MutableSequence 或 deque 会支持吗?

collections.UserList 会同步获得相同的方法以保持一致性。collections.abc.MutableSequence 保持不变,因为向 ABC 添加方法会改变所有第三方子类的接口,而通过 register() 注册的虚拟子类也不会获得该方法。collections.deque 暂不包含在此 PEP 中,以保持提案仅针对内置列表,但若 PEP 被接受,后续可作为常规变更跟进。

list.discard() 的返回值有什么用?为什么不像 set.discard() 那样返回 None?

返回布尔值可以让调用者直接使用 if lst.discard(x): 来替代先判断再删除的双重扫描写法,因为实现内部已经知道是否删除了元素,返回布尔值没有额外成本。而 set.discard() 返回 None 是历史设计,list.discard() 选择返回布尔值以提供更多信息。

为什么选择新增方法而不是给 remove() 加一个 missing_ok 参数?

提案认为独立方法更清晰,且与 set 的 remove()/discard() 命名惯例一致,让列表和集合共享同一套词汇。虽然标准库有 Path.unlink(missing_ok=True) 和 os.makedirs(exist_ok=True) 等参数式先例,但最接近的先例是 set,遵循它更合理。此外,lst.discard(x) 在调用处直接说明发生了什么,而标志参数描述的是不发生什么,可读性较差。

🏷️

标签

➡️

继续阅读