classification
Title: collections.MutableSet does not provide update method
Type: enhancement Stage:
Components: Library (Lib) Versions: Python 3.5
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: rhettinger Nosy List: akira, gvanrossum, josh.r, pitrou, rhettinger, roy.wellington
Priority: normal Keywords: patch

Created on 2014-07-27 06:06 by roy.wellington, last changed 2014-07-31 01:41 by rhettinger. This issue is now closed.

Files
File name Uploaded Description Edit
ms.py roy.wellington, 2014-07-27 06:06
set-update.patch akira, 2014-07-29 21:06 implement difference, intersection, union, difference_update, intersection_update, and update methods; mention them in Set/MutableSet documentation, and add sanity tests review
Messages (8)
msg224105 - (view) Author: Roy Wellington (roy.wellington) Date: 2014-07-27 06:06
Inheriting from collections.MutableSet mixes in several methods, however, it does not mix in a .update method. This can cause a variety of confusion if you expect a MutableSet to act like a set. Moreover, MutableMapping does provide a .update method, which makes me think this is a bug.

I've attached a file that creates a bare-bones MutableSet, and shows the difference.

Is this a bug, or is there some reason that MutableSet doesn't provide an update method?
msg224208 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2014-07-29 00:58
It's not  a bug.  Guido designed the Set ABC to use operators instead of the named methods.  The += operator for the __ior__() method that provides much of the same functionality.

In your concrete class, you can add an update() method easily:

   class MySet(Set):
       update = Set.__ior__()

Mutable mapping has an update() method because it is an essential part of the mapping API and because there is no operator equivalent as there is for the set API.
msg224216 - (view) Author: Josh Rosenberg (josh.r) * (Python triager) Date: 2014-07-29 05:53
Minor correction to example. That should be:


   class MySet(Set):
       update = Set.__ior__

(no paren after __ior__, since we're assigning, not calling)
msg224219 - (view) Author: Akira Li (akira) * Date: 2014-07-29 10:54
Set has no __ior__ method but MutableSet has:

  class MySet(MutableSet):
      update = MutableSet.__ior__

Unlike set.__ior__; MutableSet.__ior__ accepts an arbitrary iterable
and therefore MutableSet.update is redundant.

set.__ior__ doesn't accept an arbitrary iterable:

  >>> s = set()
  >>> s.update('ab')
  >>> s |= 'ab'
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: unsupported operand type(s) for |=: 'set' and 'str'
  >>> s |= set('ab')
  >>> s
  {'a', 'b'}

MutableSet.__ior__ does accept an arbitrary iterable:

  from collections.abc import MutableSet
  
  class UpperSet(MutableSet):
      """Like set() but stores items in upper case."""
      def __init__(self, iterable=()):
          self._set = set()
          self |= iterable
      def _key(self, item):
          return item.upper()
      update = MutableSet.__ior__
  
      # implement MutableSet abstract methods
      def __contains__(self, item):
          return self._key(item) in self._set
      def __iter__(self):
          return iter(self._set)
      def __len__(self):
          return len(self._set)
      def add(self, item):
          self._set.add(self._key(item))
      def discard(self, item):
          self._set.discard(self._key(item))
  
Example:

  s = UpperSet('σs')
  assert 'σ' in s and 'Σ' in s and 'S' in s and 'ς' in s and 'ſ' in s
  s.update('dzẞ') # or s |= 'dzẞ'
  assert 'Dz' in s and 'DZ' in s and 'ß' not in s and 'SS' not in s
  s |= 'ß' # or s.update('ß')
  assert s == {'Σ', 'S', 'DZ', 'ẞ', 'SS'}
msg224251 - (view) Author: Akira Li (akira) * Date: 2014-07-29 21:06
On the other hand update() method may accept multiple iterables at once:

  def update(self, *iterables):
      for it in iterables:
          self |= it

and therefore it is not equivalent to __ior__ method. In this case:
'difference', 'intersection', 'union' set methods could also be added to
Set and 'difference_update', 'intersection_update', 'update' to
MutableSet.

Negative consequences:

- no use-case?

- there are more than one way to spell an operation e.g., &= and
  intersection_update for a single iterable case

- documentation, tests, methods implementation have to be maintained in
  sync with frozenset/set

Positive:

- Set/MutableSet can be a drop in replacement for frozenset/set without
  manually reimplementing the named methods even if multiple iterables
  are not used

- documentation, tests, methods implementation are maintained only in
  stdlib and therefore bugs are fixed in a single place and the same
  behavior everywhere

I've uploaded a prototype patch that implements the named methods,
mentions them in Set/MutableSet documentation, and adds sanity tests.

If somebody provides a compelling use-case for adding the named methods
then further work on the patch could be done.
msg224276 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2014-07-30 00:16
The starting point for this feature request should be recognizing that Guido intentionally chose to not implement the named methods.

Excerpt from PEP 3119:

"This also supports the in-place mutating operations |=, &=, ^=, -=. These are concrete methods whose right operand can be an arbitrary Iterable, except for &=, whose right operand must be a Container. This ABC does not provide the named methods present on the built-in concrete set type that perform (almost) the same operations."
msg224278 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2014-07-30 00:21
This is a bit of a pity, since the named methods are generally more explicit for non-experts than the operators. The ABC could simply define default implementations for those methods to fallback on the operators.

By not providing such default implementations, the ABC makes it harder to write a user class whose API behaves like set's.
msg224283 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2014-07-30 01:38
Oh the joy of duck typing. :-(

If anything, set should be made to behave more like MutableSet by allowing arbitrary iterable arguments to the __i**__ methods.

I do not think it is a good idea to add all of the named versions of the methods to the ABC (even if it could be done by making then concrete methods implemented in terms of the operations).  If you want to work with arbitrary MutableSet objects you should restrict yourself to the operations defined by MutableSet.
History
Date User Action Args
2014-07-31 01:41:18rhettingersetstatus: open -> closed
resolution: not a bug
2014-07-30 01:38:14gvanrossumsetmessages: + msg224283
2014-07-30 00:21:31pitrousetnosy: + pitrou, gvanrossum
messages: + msg224278
2014-07-30 00:16:47rhettingersetmessages: + msg224276
2014-07-29 21:06:27akirasetfiles: + set-update.patch
keywords: + patch
messages: + msg224251
2014-07-29 10:54:36akirasetnosy: + akira
messages: + msg224219
2014-07-29 05:53:10josh.rsetnosy: + josh.r
messages: + msg224216
2014-07-29 00:58:51rhettingersetmessages: - msg224153
2014-07-29 00:58:39rhettingersetmessages: + msg224208
2014-07-28 06:19:30rhettingersetassignee: rhettinger
type: enhancement
messages: + msg224153
versions: + Python 3.5, - Python 3.3, Python 3.4
2014-07-27 08:05:29mark.dickinsonsetnosy: + rhettinger
2014-07-27 06:06:34roy.wellingtoncreate