PEP 851: Add list.discard()

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

The full PEP


This was previously discussed in Add list.discard() and deque.discard() and python/cpython#160724. I’m curious to hear what people think of this little proposal. :slight_smile:

7 Likes

Nice symmetry with sets, and a useful feature that I’ve often done in one of the two imperfect ways shown in the PEP. +1.

2 Likes

(Concern copied over from the pre-PEP thread)

The symmetry with sets is incomplete, because lists can have multiple copies of the same item, and it’s not at all clear whether discard should remove just one or all of the copies.

Overall, I’m -0 on this.

5 Likes

I would expect list.discard(x) to do the same thing as list.remove(x) when the list has multiple copies of x. That is, I would expect the only difference between discard and remove would be in suppressing the exception when the element was not present.

10 Likes

With a sufficiently degenerate object, a set can have multiple things that match a remove/discard operation, and in that case, just one of them will be removed. It stands to reason that a list should do the same.

1 Like

TBH, I don’t see the number of values as a problem. x.discard() is a non-erroring version of x.remove(). list.remove removes the first instance, so list.discard would do the same thing.

I do think @sciolist’s point about what natural operations are on a class, and what named methods imply about intended use patterns, is a bit stronger:

I pretty much never use remove(), and instead usually use iterators or comprehensions if I need to filter values, which deal with the list as a sequence instead of a bag of objects. If a bag of objects is needed, I would tend to use a different structure.

My bigger concern is that this is about mirroring a small aspect of the set API without much consideration of what the list API now will be. It will now have a non-erroring version of remove(), but not of index(), which will feel to me like a bigger asymmetry than the current asymmetry between set.remove and list.remove.

It would also be strange to make list.discard return bool without also making set.discard return bool, if the whole idea is to mimic the API. Being able to say if x.discard(y) does seem to be useful in both cases.

Anyway, no strong opinion +/- on this. I don’t think these minor inconsistencies should be blocking, just considered.

4 Likes

No strong opinion from me either, but I’m not totally convinced by the rationale for a new method over a keyword argument; if I were using list.remove and was annoyed by it raising an exception, I think the natural thing to do is check the signature for a missing_ok flag, not try to use a set method on a list.

5 Likes

An analogy can be made to string search/replace. Like str.replace() and re.sub(), an optional count parameter could be added to make it easy to discard just the first matching item or all of them. (The same goes for list.remove().)

Otherwise I predict people will implement discard-all as:

while mylist.discard(val):
    pass

which is not very efficient.

+1 from me, I like the symmetry with set (and wish we’d do more of it… set.append() would be appreciated :slight_smile: )

1 Like

How would set.append() be different to set.add()?

I don’t think this would be a positive improvement. lists aren’t sets, and they don’t need to have the same interface, nor do they really benefit from some notion of being interchangable structures, they serve different purposes.

adding a keyword-arg to .remove not to raise seems fine here (as was already suggested above).