PEP 851 – Add list.discard()
- Author:
- Stan Ulbrych <stan at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Draft
- Type:
- Standards Track
- Created:
- 27-Oct-2026
- Python-Version:
- 3.16
- Post-History:
- 12-Oct-2025, 27-Oct-2026
Abstract
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(), as
set.discard() is to set.remove().
Motivation
Removing a value that may not be present is usually written as:
if x in lst:
lst.remove(x)
or:
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().
Specification
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.
Rationale
The name follows the remove() / discard() distinction already
made by set, where discard() is the variant that tolerates a
missing item. 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. This is a real difference. discard()
deliberately shares the search and removal semantics of list.remove()
rather than those of set.discard(), and the documentation for
list.discard() in the reference implementation states this explicitly.
The name remains the least surprising choice as users who know
sets can apply that knowledge directly.
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.
Backwards Compatibility
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.
Security Implications
None.
How to Teach This
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.
Reference Implementation
A reference implementation can be found in python/cpython#160726.
Rejected Ideas
A parameter on remove()
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.
Adding discard() to collections.abc.MutableSequence
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.
deque.discard()
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.
Footnotes
Acknowledgements
Thanks to Guido van Rossum for suggesting that discard() return whether
an item was removed.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.