Skip to content

Commit f8c6294

Browse files
PEP 851: Add list.discard() (#5200)
Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
1 parent 362ca9d commit f8c6294

2 files changed

Lines changed: 191 additions & 0 deletions

File tree

‎.github/CODEOWNERS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,7 @@ peps/pep-0848.rst @markshannon
729729
peps/pep-0849.rst @JelleZijlstra
730730
peps/pep-0850/ @willingc @rgommers
731731
peps/pep-0850.rst @willingc @rgommers
732+
peps/pep-0851.rst @StanFromIreland
732733
# ...
733734
peps/pep-2026.rst @hugovk
734735
# ...

‎peps/pep-0851.rst‎

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
PEP: 851
2+
Title: Add list.discard()
3+
Author: Stan Ulbrych <stan@python.org>
4+
Discussions-To: Pending
5+
Status: Draft
6+
Type: Standards Track
7+
Created: 27-Oct-2026
8+
Python-Version: 3.16
9+
Post-History: `12-Oct-2025 <https://discuss.python.org/t/add-list-discard-and-deque-discard/104330>`__
10+
11+
12+
Abstract
13+
========
14+
15+
Add ``list.discard(x)``, which removes the first item equal to *x* if one
16+
is present and does nothing otherwise, returning whether an item was
17+
removed. It is the non-raising counterpart to :meth:`list.remove`, as
18+
:meth:`set.discard` is to :meth:`set.remove`.
19+
20+
21+
Motivation
22+
==========
23+
24+
Removing a value that may not be present is `usually written as
25+
<https://stackoverflow.com/questions/4915920/how-to-delete-an-item-in-a-list-if-it-exists>`__::
26+
27+
if x in lst:
28+
lst.remove(x)
29+
30+
or::
31+
32+
try:
33+
lst.remove(x)
34+
except ValueError:
35+
pass
36+
37+
The first form is clear but scans the list twice on a hit, and if the list
38+
is shared between threads another thread can remove the item between the
39+
two calls, turning a no-op into a :exc:`ValueError`. The second avoids both
40+
problems but obscures the intent. It is four lines for one operation, pays
41+
for raising and catching an exception on every miss, and
42+
``except ValueError`` is broad enough to hide unrelated errors from a
43+
user-defined :meth:`~object.__eq__`.
44+
45+
Both are common. A search of the 5,000 most downloaded PyPI projects [#search]_
46+
finds 1,062 matches for the first form across 411 projects and 331 for
47+
the second across 164 projects. Some of these are on other container
48+
types; in the standard library, excluding tests, two of the 29 matches are
49+
on deques and the remaining seven and 20 are on lists, for example in
50+
`logging
51+
<https://github.com/python/cpython/blob/3acc941f2b7288b212a4163ab21aa79faff9cd13/Lib/logging/__init__.py#L840-L841>`__,
52+
`warnings <https://github.com/python/cpython/blob/3acc941f2b7288b212a4163ab21aa79faff9cd13/Lib/_py_warnings.py#L326-L329>`__,
53+
`tkinter <https://github.com/python/cpython/blob/3acc941f2b7288b212a4163ab21aa79faff9cd13/Lib/tkinter/__init__.py#L490-L493>`__,
54+
`runpy <https://github.com/python/cpython/blob/3acc941f2b7288b212a4163ab21aa79faff9cd13/Lib/runpy.py#L312-L315>`__ and
55+
`asyncio <https://github.com/python/cpython/blob/3acc941f2b7288b212a4163ab21aa79faff9cd13/Lib/asyncio/subprocess.py#L100-L101>`__.
56+
Each becomes a single call that states the intent directly::
57+
58+
lst.discard(x)
59+
60+
The cost of providing this is small, as ``discard()`` reuses the existing
61+
search and removal implementation of :meth:`remove() <list.remove>`.
62+
63+
64+
Specification
65+
=============
66+
67+
``list`` gains a positional-only method ``discard(value, /)``. It removes
68+
the first item equal to *value* and returns ``True``, or returns ``False``
69+
if there is no such item. Only the absence of a matching item is reported
70+
by the return value; any other error, including a :exc:`ValueError` raised
71+
during equality comparison, propagates as an exception. The implementation
72+
shares the search loop of :meth:`remove() <list.remove>`, so which item is
73+
removed, how values are compared and the thread-safety properties are
74+
identical to ``remove()``. :class:`collections.UserList` gains the same
75+
method for consistency.
76+
77+
78+
Rationale
79+
=========
80+
81+
The name follows the ``remove()`` / ``discard()`` distinction already
82+
made by ``set``, where ``discard()`` is the variant that tolerates a
83+
missing item. It has been raised that after ``s.discard(x)`` the test
84+
``x not in s`` holds, while after ``lst.discard(x)`` it need not if *x*
85+
appears more than once. This is a real difference. ``discard()``
86+
deliberately shares the search and removal semantics of :meth:`list.remove`
87+
rather than those of :meth:`set.discard`, and the documentation for
88+
``list.discard()`` in the reference implementation states this explicitly.
89+
The name remains the least surprising choice as users who know
90+
sets can apply that knowledge directly.
91+
92+
Unlike :meth:`set.discard`, which returns ``None``, a bool is returned. This
93+
costs nothing, since the implementation already knows the outcome, and lets
94+
``if lst.discard(x):`` replace the double scan for callers who care.
95+
96+
97+
Backwards Compatibility
98+
=======================
99+
100+
:class:`list` subclasses defining their own ``discard`` keep it. Code using
101+
``hasattr(obj, "discard")`` to tell sets from lists would be affected.
102+
None was found in the standard library or the top 5,000 PyPI packages.
103+
104+
105+
Security Implications
106+
=====================
107+
108+
None.
109+
110+
111+
How to Teach This
112+
=================
113+
114+
Introduce it in the `tutorial
115+
<https://docs.python.org/3/tutorial/datastructures.html#more-on-lists>`__
116+
directly after :meth:`list.remove`, with ``remove()`` for when the item
117+
must be present and ``discard()`` for when it may not be. The same pairing
118+
already exists for :class:`set`.
119+
120+
121+
Reference Implementation
122+
========================
123+
124+
A reference implementation can be found in `python/cpython#160726
125+
<https://github.com/python/cpython/pull/160726>`__.
126+
127+
128+
Rejected Ideas
129+
==============
130+
131+
A parameter on ``remove()``
132+
---------------------------
133+
134+
A separate method is preferred over a parameter such as
135+
``remove(x, missing_ok=True)``. The standard library has precedent for both
136+
shapes, :meth:`Path.unlink(missing_ok=True) <pathlib.Path.unlink>` and
137+
:func:`os.makedirs(exist_ok=True) <os.makedirs>` on one side and
138+
:meth:`set.remove` / :meth:`set.discard` on the other, but the closest
139+
precedent is ``set``, and following it means the two container types share
140+
one vocabulary. A method also reads more clearly at the call site, since
141+
``lst.discard(x)`` says what happens while a flag describes what does not.
142+
143+
Adding ``discard()`` to ``collections.abc.MutableSequence``
144+
-----------------------------------------------------------
145+
146+
:class:`collections.abc.MutableSequence` is left unchanged, as is the case
147+
for :meth:`list.copy`. Adding ``discard()`` to the ABC would change the
148+
interface of every third-party subclass, while virtual subclasses
149+
registered with ``register()`` would not gain it at all.
150+
151+
``deque.discard()``
152+
-------------------
153+
154+
The `original discussion
155+
<https://discuss.python.org/t/add-list-discard-and-deque-discard/104330>`__
156+
also proposed :class:`collections.deque` gaining the same method. It is
157+
left out of this PEP to keep the proposal to the builtin :class:`list`.
158+
It can follow as a routine change if this PEP is accepted.
159+
160+
161+
Footnotes
162+
=========
163+
164+
.. [#search] Using `Victor Stinner's scripts
165+
<https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/>`__,
166+
modified to match across lines, with the regular expressions::
167+
168+
if ([\w.\[\]]+) in ([\w.\[\]]+):\n\s+\2\.remove\(\1\)
169+
170+
try:\n\s+([\w.\[\]]+)\.remove\(([\w.\[\]]+)\)\n\s*except ValueError:
171+
172+
The first pattern's backreferences require the same names on both
173+
lines. The counts are approximate, as the second pattern also matches
174+
handlers that do something other than ignore the absence, while neither
175+
matches variants such as compound conditions,
176+
``except (ValueError, ...)`` or a comment between the lines.
177+
178+
179+
Acknowledgements
180+
================
181+
182+
Thanks to Guido van Rossum for suggesting that ``discard()`` return whether
183+
an item was removed.
184+
185+
186+
Copyright
187+
=========
188+
189+
This document is placed in the public domain or under the
190+
CC0-1.0-Universal license, whichever is more permissive.

0 commit comments

Comments
 (0)