104

According to my interpretation of Python 2.7.2 documentation for Built-In Types 5.7 Set Types, it should be possible to remove the elements of set A from set B by passing A to set.remove(elem) or set.discard(elem)

From the documentation for 2.7.2:

Note, the elem argument to the __contains__(), remove(), and discard() methods may be a set.

I interpret this to mean that I can pass a set to remove(elem) or discard(elem) and all those elements will be removed from the target set. I would use this to do something weird like remove all vowels from a string or remove all common words from a word-frequency histogram. Here's the test code:

Python 2.7.2 (default, Jun 12 2011, 14:24:46) [M...
Type "help", "copyright", "credits" or "license"
>>> a = set(range(10))
>>> b = set(range(5,10))
>>> a
set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> b
set([8, 9, 5, 6, 7])
>>> a.remove(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: set([8, 9, 5, 6, 7])
>>> a.discard(b)
>>> a
set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>>

Which I expect to return:

>>> a
set([0, 1, 2, 3, 4])

I know I can accomplish this with a.difference(b) which returns a new set; or with a set.difference_update(other); or with set operators a -= b, which modify the set in-place.

So is this a bug in the documentation? Can set.remove(elem) actually not take a set as an argument? Or does the documentation refer to sets of sets? Given that difference_update accomplishes my interpretation, I'm guess the case is the latter.

Is that unclear enough?

EDIT After 3 years of additional (some professional) python work, and being recently drawn back to this question, I realize now what I was actually trying to do could be accomplished with:

>>> c = a.difference(b)
set([0,1,2,3,4])

which is what I was originally trying to get.

EDIT After 4 more years of python development... I realize this operation can be expressed more cleanly using set literals and the - operator; and that it is more complete to show that set difference is non-commutative.

>>> a={0,1,2,3}
>>> b={2,3,4,5}
>>> a-b
set([0, 1])
>>> b-a
set([4, 5])
cod3monk3y
  • 9,508
  • 6
  • 39
  • 54

5 Answers5

69

set1-set2

set1 = {0,1,2,3}
set2 = {2,3,4,5}

set1 - set2  # {0, 1}
set2 - set1  # {4, 5}

However, note that for whatever reason you can't "+" sets in python...

ssppjj
  • 179
  • 1
  • 10
Nic Scozzaro
  • 6,651
  • 3
  • 42
  • 46
  • 5
    Finally a simple answer! – Andrei Margeloiu May 11 '20 at 06:48
  • Incase it isn't obvious, if you need to add two sets you'd want to do set1.update(set2) it will add any elements from set2 not in set1. – Mitchell Walls May 13 '21 at 13:47
  • Useful example, however this does not answer the original concern of why `a.remove(b)` does not "remove the elements of set b from set a". Also, the addition comment is misleading: as you show, the `difference` operation is order dependent. What you're likely trying to accomplish is a "union", which is order independent, e.g. `a | b` or `b | a`. Likewise there are numerous other set operators: superset `a > b`, subset `a < b`, intersection `a & b`, etc. Hopefully that helps explains why there is no addition operator for sets. https://docs.python.org/3.8/library/stdtypes.html#set – cod3monk3y May 03 '22 at 19:37
  • Yes, I would think that `set1 + set2` would be the union. – Nic Scozzaro May 04 '22 at 01:56
29

You already answered the question. It refers to sets of sets (actually sets containing frozensets).

The paragraph you are referring to begins with:

Note, the elem argument to the __contains__(), remove(), and discard() methods may be a set.

which means that b in a.remove(b) can be a set, and then continues with:

To support searching for an equivalent frozenset, the elem set is temporarily mutated during the search and then restored. During the search, the elem set should not be read or mutated since it does not have a meaningful value.

which means that if b is a set, a.remove(b) will scan a for a frozenset equivalent to b and remove it (or throw a KeyError if it doesn't exist).

nitsas
  • 972
  • 7
  • 17
  • 2
    Thanks! Given my misinterpretation, that second sentence didn't make much sense. Choosing this as the primary answer since it helps clear up what my primary misunderstanding was. Ling's example was also very helpful. – cod3monk3y Jan 29 '12 at 21:55
10

You can't have sets of sets in Python as a set is mutable. Instead, you can have sets of frozensets. On the other hand, you can call __contains__(), remove(), and discard() with a set. See this example:

a = set([frozenset([2])])
set([2]) in a       # you get True
a.remove(set([2]))  # a is now empty

So the answer to your question is that the documentation is referring to sets of frozensets.

Jim Ferrans
  • 30,582
  • 12
  • 56
  • 83
Ling
  • 535
  • 4
  • 9
  • Very nice concise illustration of what is, to me, an uncharacteristically unclear part of the python documentation. Thank you Ling! – cod3monk3y Jan 29 '12 at 21:56
2

I'm looking at the built-in help for various versions of python (for mac). Here are the results.

  • python2.5

remove(...)
Remove an element from a set; it must be a member.
If the element is not a member, raise a KeyError.

  • python2.6

remove(...)
Remove an element from a set; it must be a member. If the element is not a member, raise a KeyError.

  • python2.7

remove(...)
Remove an element from a set; it must be a member. If the element is not a member, raise a KeyError.

The documentation you refer to, in full, actually says:

Note, the elem argument to the __contains__(), remove(), and discard() methods may be a set. To support searching for an equivalent frozenset, the elem set is temporarily mutated during the search and then restored.

This seems to be a footnote, that suggests the argument may be a set, but unless it finds a matching frozen set within the set, it will not be removed. The mention about the set being modified is so it can be hashed to look for a a matching frozen set.

Josh Smeaton
  • 47,939
  • 24
  • 129
  • 164
1

I think the documentation is referring to sets of (frozen)sets, yes.

Amber
  • 507,862
  • 82
  • 626
  • 550
  • 2
    But you can't have a set of sets; sets aren't hashable. You could have a set with a frozenset as an element, I guess. – DSM Jan 29 '12 at 21:17
  • 2
    @DSM - hence why the documentation mentions the `elem` value being mutated during the call to see if there is an equivalent `frozenset`. – Amber Jan 29 '12 at 21:19