You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I suggest adding a list.discard(x) method, which removes the first occurrence of x if present and does nothing otherwise, mirroring set.discard(). This was previously discussed on Discourse.
Removing a value that may not be in a list is a reasonably common pattern, and is generally written today as either:
ifxinlst:
lst.remove(x)
or
try:
lst.remove(x)
exceptValueError:
pass
The in version scans the list twice on a hit, and the try/except has to deal with the exception. A quick pyperf run on a 1000-element list, removing the last element:
discard: 20.0 us
in + remove: 38.0 us
try/except: 21.0 us
Importantly, the implementation is cheap. The search-and-remove loop in list_remove_impl can be moved into a shared helper so remove() and discard() become thin wrappers around it. There is little extra maintenance cost since the two methods share the same logic.
For now I am just proposing adding the method to list, deque can be done as a follow-up.
The timings you give aren't very convincing as an argument for adding a new method; it's hardly any faster than the try-except route.
Indeed, and I don't think performance is the main argument. The gain over try/except is small, and it is only the in + remove() idiom that pays for a second scan. However, there are additional reasons:
It is a reasonably common operation that currently takes three or four lines (or a double scan) to express. lst.discard(x) states the intent directly, so it is admittedly a bit of syntactic sugar, but sugar that removes a try/except block from the reader's path.
It is cheap to implement and maintain (the removal logic already exists).
It is consistent with set.discard(), which already has these semantics, and learners are familiar with. Similar reasoning was used for adding int.is_integer().
Agreed that a PEP seems appropriate here; there are a few questions to settle.
Off the top of my head:
why only list? deque was also mentioned in the inciting discussion, and there could even be an argument for a tuple.discard that returns a new tuple (though tuple.remove does not currently exist).
what happens in [1, 1, 2, 3, 5].discard(1)? Should you get [2, 3, 5] or [1, 2, 3, 5]? What about [1, 2, 3, 5, 1].discard(1)?
deque is a possible follow-up, I left it out here to keep the scope small (but it seems this needed more discussion anyway ;-).
tuple.discard() doesn't fit, like set.discard(), this is an in-place mutation, and tuples have no remove() to mirror. A method returning a new tuple would be a different kind of operation.
what happens in [1, 1, 2, 3, 5].discard(1)?
Exactly the same as list.remove(), the first occurrence is removed, so [1, 1, 2, 3, 5].discard(1) gives [1, 2, 3, 5] and [1, 2, 3, 5, 1].discard(1) gives [2, 3, 5, 1]. The only difference from remove() is that a missing value is a no-op instead of a ValueError. The implementation shares the search-and-remove loop with remove(), so the semantics can't drift.
Can it please return True iff an item was removed?
I like it, and the implementation is simple. It is a minor departure from set.discard, but I don't think that will be much of an issue in practice. Added to the PR in 687d42d.
Exactly the same as list.remove(), the first occurrence is removed, so [1, 1, 2, 3, 5].discard(1) gives [1, 2, 3, 5] and [1, 2, 3, 5, 1].discard(1) gives [2, 3, 5, 1]. The only difference from remove() is that a missing value is a no-op instead of a ValueError. The implementation shares the search-and-remove loop with remove(), so the semantics can't drift.
That makes perfect sense from an implementation point of view and is a perfectly reasonable choice to make. I just worry that the name "discard" may imply the other interpretation: after all, set.discard does ensure that the value will no longer be in the set after return. Documentation may be enough to overcome that, or maybe I'm over-worrying :)
FTR, I do like the idea overall and am in favor, particularly with Guido's suggestion to return whether anything changed.
I've been asked to review this. I don't think we need a PEP (Stan answered the objections sensibly, IMO) but I want to wait a little until we're reasonably sure there are no objections here.
I'm not sure if I can ensure all the technical details are correct, but I suppose I can approve it trusting that Stan knows the implementation better than I do at this point. (@StanFromIreland, did you make sure your new code avoids free-threading race conditions?)
Regarding deque, can we change the Issue title to include deque.discard()? It should be a separate PR, since the implementation is entirely different.
I want to wait a little until we're reasonably sure there are no objections here.
Sounds good, I'm happy to wait a few weeks :-) And no pressure to review. (Zach, could you however please take a look at the docs? Do you think they should be more explicit about the removal behaviour, or do they address your concerns?)
did you make sure your new code avoids free-threading race conditions?
discard() shares its implementation with list.remove(), so it runs under the same critical section and has the same thread-safety guarantees. That is, it cannot corrupt the list, but it is not atomic, since __eq__ can run arbitrary code and release the lock. I documented this (alongside remove()) on the thread safety page.
Regarding deque, can we change the Issue title to include deque.discard()?
Edited, I will open a separate PR for deque.discard() once this one has settled.
I personally would like a PEP. The other cited cases for modifying builtins involved providing genuinely new functionality, so if there was a compelling use case, there was little to argue against. In contrast, this is a convenience function, meaning that there's a lot more to discuss about what users actually want. I have several comments, which should be addressed in a PEP:
It'd be nice to see some concrete numbers on how many things are using the in or try workaround (via a code search).
Is discard really the best name? We're already deviating from what set.discard does by returning True, so there's a false symmetry here.
Why not just add a noexcept=True parameter or something like that to remove()?
There are a bunch of other things that have remove methods where this could apply, beyond just deque (such as array.array and bytearray). Should those get discard() methods too? Should we just add discard() to the whole collections.abc.MutableSequence interface?
If this many people would like a PEP, I can write one, though personally I don't see what it would add over the discussion already happening here. (The questions are the same, we would just be answering them in a different thread.)
Many of the methods on builtins are convenience functions, e.g. bytearray.take_bytes or int.is_integer() (was added purely for consistency with float, which seems a weaker justification than a pattern people already write regularly today).
Is discard really the best name?
Personally, I find it clear. "Discard" carries the implication that you don't care whether the item was there, which is exactly the semantics, and it has precedent in set.discard(), so learners can connect the two. The return value is extra information, but not a change in what the method does.
Why not just add a noexcept=True parameter
Also possible, but nothing else in the list API works that way. There are several ways to spell this, and I don't think any of the alternatives are drastically better than the others, but this one has existing precedent.
Should those get discard() methods too?
deque is a planned follow-up. array.array and bytearray could follow if there is demand, but I don't think the MutableSequence ABC should change, that forces a new method onto every third-party subclass, which is a much bigger compatibility question than adding a method to the concrete types (and... based on recent comments, it seems there's little support for just a list method).
Zach, could you however please take a look at the docs? Do you think they should be more explicit about the removal behaviour, or do they address your concerns?
The docs in the PR are fine; they document the behavior explicitly and accurately. My concern is more philosophical: is any documentation (assuming it to be perfect) enough to outweigh the likelihood that users would confidently guess the behavior incorrectly from just the name? There's no objective measure for this that I can see.
"Discard" carries the implication that you don't care whether the item was there, which is exactly the semantics
It also carries the implication that you just want it gone, which is the root of my concern.
My point is more that there are clearly some questions here, which is why I think this needs a wider discussion. It's not fair to users to make these kinds of decisions without giving them a chance to voice their opinions.
Many of the methods on builtins are convenience functions, e.g. bytearray.take_bytes or int.is_integer() (was added purely for consistency with float, which seems a weaker justification than a pattern people already write regularly today).
bytearray.take_bytes is precisely not a convenience function. It was added for creating bytes objects from a bytearray without a copy. is_integer was also not entirely for convenience -- it was added for duck typing purposes, so you don't have to do an instance check when looking at something that is potentially an int object. My point is that I don't think they are comparable examples here.
I don't think the MutableSequence ABC should change, that forces a new method onto every third-party subclass, which is a much bigger compatibility question than adding a method to the concrete types
Keep in mind that we are also forcing the method onto every third-party list subclass! If there are legitimate backward compatibility reasons to avoid adding discard to the MutableSequence interface, I'd expect those to also apply to list subclasses.
Activity
JelleZijlstra commentedon Oct 26, 2026
I believe adding methods to a builtin generally requires a PEP, like for
str.removeprefix.The timings you give aren't very convincing as an argument for adding a new method; it's hardly any faster than the try-except route.
StanFromIreland commentedon Oct 26, 2026
int.is_integer(),bytearray.take_bytes(),bytearray.resize()andfloat.from_number()/complex.from_number()were all added recently without a PEP. Given that this is a similarly small addition, I thought it would be fine to go through an issue, provided there is general consensus here.Indeed, and I don't think performance is the main argument. The gain over
try/exceptis small, and it is only thein+remove()idiom that pays for a second scan. However, there are additional reasons:lst.discard(x)states the intent directly, so it is admittedly a bit of syntactic sugar, but sugar that removes atry/exceptblock from the reader's path.set.discard(), which already has these semantics, and learners are familiar with. Similar reasoning was used for addingint.is_integer().zware commentedon Oct 26, 2026
Agreed that a PEP seems appropriate here; there are a few questions to settle.
Off the top of my head:
list?dequewas also mentioned in the inciting discussion, and there could even be an argument for atuple.discardthat returns a new tuple (thoughtuple.removedoes not currently exist).[1, 1, 2, 3, 5].discard(1)? Should you get[2, 3, 5]or[1, 2, 3, 5]? What about[1, 2, 3, 5, 1].discard(1)?gvanrossum commentedon Oct 26, 2026
Can it please return True iff an item was removed? I'd like to write
to remove all occurrences.
I agree with adding this for convenience.
StanFromIreland commentedon Oct 26, 2026
dequeis a possible follow-up, I left it out here to keep the scope small (but it seems this needed more discussion anyway ;-).tuple.discard()doesn't fit, likeset.discard(), this is an in-place mutation, and tuples have noremove()to mirror. A method returning a new tuple would be a different kind of operation.Exactly the same as
list.remove(), the first occurrence is removed, so[1, 1, 2, 3, 5].discard(1)gives[1, 2, 3, 5]and[1, 2, 3, 5, 1].discard(1)gives[2, 3, 5, 1]. The only difference fromremove()is that a missing value is a no-op instead of aValueError. The implementation shares the search-and-remove loop withremove(), so the semantics can't drift.StanFromIreland commentedon Oct 26, 2026
I like it, and the implementation is simple. It is a minor departure from
set.discard, but I don't think that will be much of an issue in practice. Added to the PR in 687d42d.zware commentedon Oct 26, 2026
That makes perfect sense from an implementation point of view and is a perfectly reasonable choice to make. I just worry that the name "discard" may imply the other interpretation: after all,
set.discarddoes ensure that the value will no longer be in the set after return. Documentation may be enough to overcome that, or maybe I'm over-worrying :)FTR, I do like the idea overall and am in favor, particularly with Guido's suggestion to return whether anything changed.
gvanrossum commentedon Oct 26, 2026
I've been asked to review this. I don't think we need a PEP (Stan answered the objections sensibly, IMO) but I want to wait a little until we're reasonably sure there are no objections here.
I'm not sure if I can ensure all the technical details are correct, but I suppose I can approve it trusting that Stan knows the implementation better than I do at this point. (@StanFromIreland, did you make sure your new code avoids free-threading race conditions?)
Regarding deque, can we change the Issue title to include deque.discard()? It should be a separate PR, since the implementation is entirely different.
[-]Add `list.discard()`[/-][+]Add `list.discard()` and `deque.discard()`[/+]StanFromIreland commentedon Oct 26, 2026
Sounds good, I'm happy to wait a few weeks :-) And no pressure to review. (Zach, could you however please take a look at the docs? Do you think they should be more explicit about the removal behaviour, or do they address your concerns?)
discard()shares its implementation withlist.remove(), so it runs under the same critical section and has the same thread-safety guarantees. That is, it cannot corrupt the list, but it is not atomic, since__eq__can run arbitrary code and release the lock. I documented this (alongsideremove()) on the thread safety page.Edited, I will open a separate PR for
deque.discard()once this one has settled.ZeroIntensity commentedon Oct 26, 2026
I personally would like a PEP. The other cited cases for modifying builtins involved providing genuinely new functionality, so if there was a compelling use case, there was little to argue against. In contrast, this is a convenience function, meaning that there's a lot more to discuss about what users actually want. I have several comments, which should be addressed in a PEP:
inortryworkaround (via a code search).discardreally the best name? We're already deviating from whatset.discarddoes by returningTrue, so there's a false symmetry here.noexcept=Trueparameter or something like that toremove()?removemethods where this could apply, beyond justdeque(such asarray.arrayandbytearray). Should those getdiscard()methods too? Should we just adddiscard()to the wholecollections.abc.MutableSequenceinterface?StanFromIreland commentedon Oct 26, 2026
If this many people would like a PEP, I can write one, though personally I don't see what it would add over the discussion already happening here. (The questions are the same, we would just be answering them in a different thread.)
There are a few examples from the stdlib earlier in the Discourse thread. I can try run a search later.
Many of the methods on builtins are convenience functions, e.g.
bytearray.take_bytesorint.is_integer()(was added purely for consistency withfloat, which seems a weaker justification than a pattern people already write regularly today).Personally, I find it clear. "Discard" carries the implication that you don't care whether the item was there, which is exactly the semantics, and it has precedent in
set.discard(), so learners can connect the two. The return value is extra information, but not a change in what the method does.Also possible, but nothing else in the
listAPI works that way. There are several ways to spell this, and I don't think any of the alternatives are drastically better than the others, but this one has existing precedent.dequeis a planned follow-up.array.arrayandbytearraycould follow if there is demand, but I don't think theMutableSequenceABC should change, that forces a new method onto every third-party subclass, which is a much bigger compatibility question than adding a method to the concrete types (and... based on recent comments, it seems there's little support for just a list method).zware commentedon Oct 26, 2026
The docs in the PR are fine; they document the behavior explicitly and accurately. My concern is more philosophical: is any documentation (assuming it to be perfect) enough to outweigh the likelihood that users would confidently guess the behavior incorrectly from just the name? There's no objective measure for this that I can see.
It also carries the implication that you just want it gone, which is the root of my concern.
ZeroIntensity commentedon Oct 26, 2026
My point is more that there are clearly some questions here, which is why I think this needs a wider discussion. It's not fair to users to make these kinds of decisions without giving them a chance to voice their opinions.
bytearray.take_bytesis precisely not a convenience function. It was added for creatingbytesobjects from abytearraywithout a copy.is_integerwas also not entirely for convenience -- it was added for duck typing purposes, so you don't have to do an instance check when looking at something that is potentially anintobject. My point is that I don't think they are comparable examples here.Keep in mind that we are also forcing the method onto every third-party
listsubclass! If there are legitimate backward compatibility reasons to avoid addingdiscardto theMutableSequenceinterface, I'd expect those to also apply tolistsubclasses.Gerardwx commentedon Oct 26, 2026
I am willing to write a draft of the PEP.
StanFromIreland commentedon Oct 27, 2026
Sorry @Gerardwx I had already started on a PEP since I saw quite a few people requested one.
PEP 851 is now live, please discuss at https://discuss.python.org/t/pep-851-add-list-discard/109701. :-)