Skip to content

Add list.discard() and deque.discard() #160724

Open

Description

@StanFromIreland

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:

if x in lst:
    lst.remove(x)

or

try:
    lst.remove(x)
except ValueError:
    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.

Linked PRs

Activity

  1. JelleZijlstra commented on Oct 26, 2026

    @JelleZijlstra
    Member

    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.

  2. StanFromIreland commented on Oct 26, 2026

    @StanFromIreland
    MemberAuthor

    I believe adding methods to a builtin generally requires a PEP, like for str.removeprefix.

    int.is_integer(), bytearray.take_bytes(), bytearray.resize() and float.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.

    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().
  3. zware commented on Oct 26, 2026

    @zware
    Member

    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)?
  4. gvanrossum commented on Oct 26, 2026

    @gvanrossum
    Member

    Can it please return True iff an item was removed? I'd like to write

    while l.discard(a):
        pass

    to remove all occurrences.

    I agree with adding this for convenience.

  5. StanFromIreland commented on Oct 26, 2026

    @StanFromIreland
    MemberAuthor

    why only list?

    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.

  6. StanFromIreland commented on Oct 26, 2026

    @StanFromIreland
    MemberAuthor

    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.

  7. zware commented on Oct 26, 2026

    @zware
    Member

    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.

  8. gvanrossum commented on Oct 26, 2026

    @gvanrossum
    Member

    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.

  9. changed the title [-]Add `list.discard()`[/-] [+]Add `list.discard()` and `deque.discard()`[/+] on Oct 26, 2026
  10. StanFromIreland commented on Oct 26, 2026

    @StanFromIreland
    MemberAuthor

    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.

  11. ZeroIntensity commented on Oct 26, 2026

    @ZeroIntensity
    Member

    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:

    1. It'd be nice to see some concrete numbers on how many things are using the in or try workaround (via a code search).
    2. 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.
    3. Why not just add a noexcept=True parameter or something like that to remove()?
    4. 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?
  12. StanFromIreland commented on Oct 26, 2026

    @StanFromIreland
    MemberAuthor

    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.)

    It'd be nice to see some concrete numbers

    There are a few examples from the stdlib earlier in the Discourse thread. I can try run a search later.

    this is a convenience function

    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).

  13. zware commented on Oct 26, 2026

    @zware
    Member

    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.

  14. ZeroIntensity commented on Oct 26, 2026

    @ZeroIntensity
    Member

    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.

  15. Gerardwx commented on Oct 26, 2026

    @Gerardwx
    Contributor

    I am willing to write a draft of the PEP.

  16. StanFromIreland commented on Oct 27, 2026

    @StanFromIreland
    MemberAuthor

    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. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

interpreter-core(Objects, Python, Grammar, and Parser dirs)type-featureA feature request or enhancement

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions