
PEP 851 – Add List.discard()
Add list.discard(x), which removes the first item equal to x if one
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
Add list.discard(x), which removes the first item equal to x if one is present and does nothing otherwise, returning whether an item was removed. It is the non-raising counterpart to list.remove(), which currently has none. The name is taken from set.discard(), which already means “remove if present” in Python.
Removing a value that may not be present is usually written as:
try: lst.remove(x) except ValueError: pass
The first form is clear but scans the list twice on a hit, and if the list is shared between threads another thread can remove the item between the two calls, turning a no-op into a ValueError. The second avoids both problems but obscures the intent. It is four lines for one operation, pays for raising and catching an exception on every miss, and except ValueError is broad enough to hide unrelated errors from a user-defined eq().
Both are common. A search of the 5,000 most downloaded PyPI projects [1] finds 1,062 matches for the first form across 411 projects and 331 for the second across 164 projects. Some of these are on other container types; in the standard library, excluding tests, two of the 29 matches are on deques and the remaining seven and 20 are on lists, for example in logging, warnings, tkinter, runpy and asyncio. Each becomes a single call that states the intent directly:
lst.discard(x)
The cost of providing this is small, as discard() reuses the existing search and removal implementation of remove().
list gains a positional-only method discard(value, /). It removes the first item equal to value and returns True, or returns False if there is no such item. Only the absence of a matching item is reported by the return value; any other error, including a ValueError raised during equality comparison, propagates as an exception. The implementation shares the search loop of remove(), so which item is removed, how values are compared and the thread-safety properties are identical to remove(). collections.UserList gains the same method for consistency.
Symmetry with set is not the goal. The motivation is the missing non-raising form of list.remove(). set only supplies the name, since set.discard() already means “remove if present” in Python.
It has been raised that after s.discard(x) the test x not in s holds, while after lst.discard(x) it need not if x appears more than once. That is a property of sets, not of discard(), and list.remove() has the same behaviour today. discard() follows remove() here, and its will documentation clearly say so.
Unlike set.discard(), which returns None, a bool is returned. This costs nothing, since the implementation already knows the outcome, and lets if lst.discard(x): replace the double scan for callers who care.
list subclasses defining their own discard keep it. Code using hasattr(obj, "discard") to tell sets from lists would be affected. None was found in the standard library or the top 5,000 PyPI packages.
Introduce it in the tutorial directly after list.remove(), with remove() for when the item must be present and discard() for when it may not be. The same pairing already exists for set.
A reference implementation can be found in python/cpython#160726.
A separate method is preferred over a parameter such as remove(x, missing_ok=True). The standard library has precedent for both shapes, Path.unlink(missing_ok=True) and os.makedirs(exist_ok=True) on one side and set.remove() / set.discard() on the other, but the closest precedent is set, and following it means the two container types share one vocabulary. A method also reads more clearly at the call site, since lst.discard(x) says what happens while a flag describes what does not.
collections.abc.MutableSequence is left unchanged, as is the case for list.copy(). Adding discard() to the ABC would change the interface of every third-party subclass, while virtual subclasses registered with register() would not gain it at all.
The original discussion also proposed collections.deque gaining the same method. It is left out of this PEP to keep the proposal to the builtin list. It can follow as a routine change if this PEP is accepted.
if ([\w.[]]+) in ([\w.[]]+):\n\s+\2.remove(\1)
try:\n\s+([\w.[]]+).remove(([\w.[]]+))\n\s*except ValueError:
The first pattern’s backreferences require the same names on both lines. The counts are approximate, as the second pattern also matches handlers that do something other than ignore the absence, while neither matches variants such as compound conditions, except (ValueError, ...) or a comment between the lines.
Thanks to Guido van Rossum for suggesting that discard() return whether an item was removed.
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。