consistent across versions.
* Relaxed the argument restrictions for non-operator methods. They now
allow any iterable instead of requiring a set. This makes the module
a little easier to use and paves the way for an efficient C
implementation which can take better advantage of iterable arguments
while screening out immutables.
* Added a PendingDeprecationWarning for Set.update() because it now
duplicates Set.union_update()
* Adapted the tests and docs to include the above changes.
* Added more test coverage including testing identities and checking
to make sure non-restartable generators work as arguments.
Instances of \class{Set} and \class{ImmutableSet} both provide
the following operations:
-\begin{tableii}{c|l}{code}{Operation}{Result}
- \lineii{len(\var{s})}{cardinality of set \var{s}}
+\begin{tableiii}{c|c|l}{code}{Operation}{Equivalent}{Result}
+ \lineiii{len(\var{s})}{}{cardinality of set \var{s}}
\hline
- \lineii{\var{x} in \var{s}}
+ \lineiii{\var{x} in \var{s}}{}
{test \var{x} for membership in \var{s}}
- \lineii{\var{x} not in \var{s}}
+ \lineiii{\var{x} not in \var{s}}{}
{test \var{x} for non-membership in \var{s}}
- \lineii{\var{s}.issubset(\var{t})}
- {test whether every element in \var{s} is in \var{t};
- \code{\var{s} <= \var{t}} is equivalent}
- \lineii{\var{s}.issuperset(\var{t})}
- {test whether every element in \var{t} is in \var{s};
- \code{\var{s} >= \var{t}} is equivalent}
+ \lineiii{\var{s}.issubset(\var{t})}{\code{\var{s} <= \var{t}}}
+ {test whether every element in \var{s} is in \var{t}}
+ \lineiii{\var{s}.issuperset(\var{t})}{\code{\var{s} >= \var{t}}}
+ {test whether every element in \var{t} is in \var{s}}
\hline
- \lineii{\var{s} | \var{t}}
- {new set with elements from both \var{s} and \var{t}}
- \lineii{\var{s}.union(\var{t})}
+ \lineiii{\var{s}.union(\var{t})}{\var{s} | \var{t}}
{new set with elements from both \var{s} and \var{t}}
- \lineii{\var{s} \&\ \var{t}}
- {new set with elements common to \var{s} and \var{t}}
- \lineii{\var{s}.intersection(\var{t})}
+ \lineiii{\var{s}.intersection(\var{t})}{\var{s} \&\ \var{t}}
{new set with elements common to \var{s} and \var{t}}
- \lineii{\var{s} - \var{t}}
+ \lineiii{\var{s}.difference(\var{t})}{\var{s} - \var{t}}
{new set with elements in \var{s} but not in \var{t}}
- \lineii{\var{s}.difference(\var{t})}
- {new set with elements in \var{s} but not in \var{t}}
- \lineii{\var{s} \^\ \var{t}}
- {new set with elements in either \var{s} or \var{t} but not both}
- \lineii{\var{s}.symmetric_difference(\var{t})}
+ \lineiii{\var{s}.symmetric_difference(\var{t})}{\var{s} \^\ \var{t}}
{new set with elements in either \var{s} or \var{t} but not both}
- \lineii{\var{s}.copy()}
+ \lineiii{\var{s}.copy()}{}
{new set with a shallow copy of \var{s}}
-\end{tableii}
+\end{tableiii}
+
+Note, this non-operator versions of \method{union()},
+\method{intersection()}, \method{difference()}, and
+\method{symmetric_difference()} will accept any iterable as an argument.
+In contrast, their operator based counterparts require their arguments to
+be sets. This precludes error-prone constructions like
+\code{Set('abc') \&\ 'cbs'} in favor of the more readable
+\code{Set('abc').intersection('cbs')}.
+\versionchanged[Formerly all arguments were required to be sets]{2.3.1}
In addition, both \class{Set} and \class{ImmutableSet}
support set to set comparisons. Two sets are equal if and only if
The subset and equality comparisons do not generalize to a complete
ordering function. For example, any two disjoint sets are not equal and
-are not subsets of each other, so \emph{none} of the following are true:
-\code{\var{a}<\var{b}}, \code{\var{a}==\var{b}}, or \code{\var{a}>\var{b}}.
+are not subsets of each other, so \emph{all} of the following return
+\code{False}: \code{\var{a}<\var{b}}, \code{\var{a}==\var{b}}, or
+\code{\var{a}>\var{b}}.
Accordingly, sets do not implement the \method{__cmp__} method.
Since sets only define partial ordering (subset relationships), the output
The following table lists operations available in \class{ImmutableSet}
but not found in \class{Set}:
-\begin{tableii}{c|l|c}{code}{Operation}{Result}
+\begin{tableii}{c|l}{code}{Operation}{Result}
\lineii{hash(\var{s})}{returns a hash value for \var{s}}
\end{tableii}
The following table lists operations available in \class{Set}
but not found in \class{ImmutableSet}:
-\begin{tableii}{c|l}{code}{Operation}{Result}
- \lineii{\var{s} |= \var{t}}
+\begin{tableiii}{c|c|l}{code}{Operation}{Equivalent}{Result}
+ \lineiii{\var{s}.union_update(\var{t})}
+ {\var{s} |= \var{t}}
{return set \var{s} with elements added from \var{t}}
- \lineii{\var{s}.union_update(\var{t})}
- {return set \var{s} with elements added from \var{t}}
- \lineii{\var{s} \&= \var{t}}
- {return set \var{s} keeping only elements also found in \var{t}}
- \lineii{\var{s}.intersection_update(\var{t})}
+ \lineiii{\var{s}.intersection_update(\var{t})}
+ {\var{s} \&= \var{t}}
{return set \var{s} keeping only elements also found in \var{t}}
- \lineii{\var{s} -= \var{t}}
+ \lineiii{\var{s}.difference_update(\var{t})}
+ {\var{s} -= \var{t}}
{return set \var{s} after removing elements found in \var{t}}
- \lineii{\var{s}.difference_update(\var{t})}
- {return set \var{s} after removing elements found in \var{t}}
- \lineii{\var{s} \textasciicircum= \var{t}}
- {return set \var{s} with elements from \var{s} or \var{t}
- but not both}
- \lineii{\var{s}.symmetric_difference_update(\var{t})}
+ \lineiii{\var{s}.symmetric_difference_update(\var{t})}
+ {\var{s} \textasciicircum= \var{t}}
{return set \var{s} with elements from \var{s} or \var{t}
but not both}
\hline
- \lineii{\var{s}.add(\var{x})}
+ \lineiii{\var{s}.add(\var{x})}{}
{add element \var{x} to set \var{s}}
- \lineii{\var{s}.remove(\var{x})}
- {remove \var{x} from set \var{s}}
- \lineii{\var{s}.discard(\var{x})}
+ \lineiii{\var{s}.remove(\var{x})}{}
+ {remove \var{x} from set \var{s}; raises KeyError if not present}
+ \lineiii{\var{s}.discard(\var{x})}{}
{removes \var{x} from set \var{s} if present}
- \lineii{\var{s}.pop()}
- {remove and return an arbitrary element from \var{s}}
- \lineii{\var{s}.update(\var{t})}
- {add elements from \var{t} to set \var{s}}
- \lineii{\var{s}.clear()}
+ \lineiii{\var{s}.pop()}{}
+ {remove and return an arbitrary element from \var{s}; raises
+ KeyError if empty}
+ \lineiii{\var{s}.clear()}{}
{remove all elements from set \var{s}}
-\end{tableii}
+\end{tableiii}
+
+\versionchanged[Earlier versions had an \method{update()} method; use
+ \method{union_update()} instead]{2.3.1}
+
+Note, this non-operator versions of \method{union_update()},
+\method{intersection_update()}, \method{difference_update()}, and
+\method{symmetric_difference_update()} will accept any iterable as
+an argument.
+\versionchanged[Formerly all arguments were required to be sets]{2.3.1}
\subsection{Example \label{set-example}}
>>> from sets import Set
>>> engineers = Set(['John', 'Jane', 'Jack', 'Janice'])
>>> programmers = Set(['Jack', 'Sam', 'Susan', 'Janice'])
->>> management = Set(['Jane', 'Jack', 'Susan', 'Zack'])
->>> employees = engineers | programmers | management # union
->>> engineering_management = engineers & programmers # intersection
->>> fulltime_management = management - engineers - programmers # difference
->>> engineers.add('Marvin') # add element
+>>> managers = Set(['Jane', 'Jack', 'Susan', 'Zack'])
+>>> employees = engineers | programmers | managers # union
+>>> engineering_management = engineers & managers # intersection
+>>> fulltime_management = managers - engineers - programmers # difference
+>>> engineers.add('Marvin') # add element
>>> print engineers
Set(['Jane', 'Marvin', 'Janice', 'John', 'Jack'])
>>> employees.issuperset(engineers) # superset test
False
->>> employees.update(engineers) # update from another set
+>>> employees.union_update(engineers) # update from another set
>>> employees.issuperset(engineers)
True
>>> for group in [engineers, programmers, management, employees]:
"""
if not isinstance(other, BaseSet):
return NotImplemented
- result = self.__class__()
- result._data = self._data.copy()
- result._data.update(other._data)
- return result
+ return self.union(other)
def union(self, other):
"""Return the union of two sets as a new set.
(I.e. all elements that are in either set.)
"""
- return self | other
+ result = self.__class__(self)
+ result._update(other)
+ return result
def __and__(self, other):
"""Return the intersection of two sets as a new set.
"""
if not isinstance(other, BaseSet):
return NotImplemented
- if len(self) <= len(other):
- little, big = self, other
- else:
- little, big = other, self
- common = ifilter(big._data.has_key, little)
- return self.__class__(common)
+ return self.intersection(other)
def intersection(self, other):
"""Return the intersection of two sets as a new set.
(I.e. all elements that are in both sets.)
"""
- return self & other
+ if not isinstance(other, BaseSet):
+ other = Set(other)
+ if len(self) <= len(other):
+ little, big = self, other
+ else:
+ little, big = other, self
+ common = ifilter(big._data.has_key, little)
+ return self.__class__(common)
def __xor__(self, other):
"""Return the symmetric difference of two sets as a new set.
"""
if not isinstance(other, BaseSet):
return NotImplemented
+ return self.symmetric_difference(other)
+
+ def symmetric_difference(self, other):
+ """Return the symmetric difference of two sets as a new set.
+
+ (I.e. all elements that are in exactly one of the sets.)
+ """
result = self.__class__()
data = result._data
value = True
selfdata = self._data
- otherdata = other._data
+ try:
+ otherdata = other._data
+ except AttributeError:
+ otherdata = Set(other)._data
for elt in ifilterfalse(otherdata.has_key, selfdata):
data[elt] = value
for elt in ifilterfalse(selfdata.has_key, otherdata):
data[elt] = value
return result
- def symmetric_difference(self, other):
- """Return the symmetric difference of two sets as a new set.
-
- (I.e. all elements that are in exactly one of the sets.)
- """
- return self ^ other
-
def __sub__(self, other):
"""Return the difference of two sets as a new Set.
"""
if not isinstance(other, BaseSet):
return NotImplemented
- result = self.__class__()
- data = result._data
- value = True
- for elt in ifilterfalse(other._data.has_key, self):
- data[elt] = value
- return result
+ return self.difference(other)
def difference(self, other):
"""Return the difference of two sets as a new Set.
(I.e. all elements that are in this set and not in the other.)
"""
- return self - other
+ result = self.__class__()
+ data = result._data
+ try:
+ otherdata = other._data
+ except AttributeError:
+ otherdata = Set(other)._data
+ value = True
+ for elt in ifilterfalse(otherdata.has_key, self):
+ data[elt] = value
+ return result
# Membership test
def union_update(self, other):
"""Update a set with the union of itself and another."""
- self |= other
+ self._update(other)
def __iand__(self, other):
"""Update a set with the intersection of itself and another."""
def intersection_update(self, other):
"""Update a set with the intersection of itself and another."""
- self &= other
+ if isinstance(other, BaseSet):
+ self &= other
+ else:
+ self._data = (self.intersection(other))._data
def __ixor__(self, other):
"""Update a set with the symmetric difference of itself and another."""
self._binary_sanity_check(other)
+ self.symmetric_difference_update(other)
+ return self
+
+ def symmetric_difference_update(self, other):
+ """Update a set with the symmetric difference of itself and another."""
data = self._data
value = True
+ if not isinstance(other, BaseSet):
+ other = Set(other)
for elt in other:
if elt in data:
del data[elt]
else:
data[elt] = value
- return self
-
- def symmetric_difference_update(self, other):
- """Update a set with the symmetric difference of itself and another."""
- self ^= other
def __isub__(self, other):
"""Remove all elements of another set from this set."""
self._binary_sanity_check(other)
- data = self._data
- for elt in ifilter(data.has_key, other):
- del data[elt]
+ self.difference_update(other)
return self
def difference_update(self, other):
"""Remove all elements of another set from this set."""
- self -= other
+ data = self._data
+ if not isinstance(other, BaseSet):
+ other = Set(other)
+ for elt in ifilter(data.has_key, other):
+ del data[elt]
# Python dict-like mass mutations: update, clear
def update(self, iterable):
"""Add all values from an iterable (such as a list or file)."""
+ import warnings
+ warnings.warn("The update() method is going to be deprecated; "
+ "Use union_update() instead",
+ PendingDeprecationWarning)
self._update(iterable)
def clear(self):
self.assertRaises(TypeError, Set, baditer())
def test_instancesWithoutException(self):
- """All of these iterables should load without exception."""
+ # All of these iterables should load without exception.
Set([1,2,3])
Set((1,2,3))
Set({'one':1, 'two':2, 'three':3})
self.failUnless(v in popped)
def test_update_empty_tuple(self):
- self.set.update(())
+ self.set.union_update(())
self.assertEqual(self.set, Set(self.values))
def test_update_unit_tuple_overlap(self):
- self.set.update(("a",))
+ self.set.union_update(("a",))
self.assertEqual(self.set, Set(self.values))
def test_update_unit_tuple_non_overlap(self):
- self.set.update(("a", "z"))
+ self.set.union_update(("a", "z"))
self.assertEqual(self.set, Set(self.values + ["z"]))
#==============================================================================
self.assertRaises(TypeError, lambda: self.other > self.set)
self.assertRaises(TypeError, lambda: self.other >= self.set)
- def test_union_update(self):
+ def test_union_update_operator(self):
try:
self.set |= self.other
except TypeError:
else:
self.fail("expected TypeError")
+ def test_union_update(self):
+ if self.otherIsIterable:
+ self.set.union_update(self.other)
+ else:
+ self.assertRaises(TypeError, self.set.union_update, self.other)
+
def test_union(self):
self.assertRaises(TypeError, lambda: self.set | self.other)
self.assertRaises(TypeError, lambda: self.other | self.set)
+ if self.otherIsIterable:
+ self.set.union(self.other)
+ else:
+ self.assertRaises(TypeError, self.set.union, self.other)
- def test_intersection_update(self):
+ def test_intersection_update_operator(self):
try:
self.set &= self.other
except TypeError:
else:
self.fail("expected TypeError")
+ def test_intersection_update(self):
+ if self.otherIsIterable:
+ self.set.intersection_update(self.other)
+ else:
+ self.assertRaises(TypeError,
+ self.set.intersection_update,
+ self.other)
+
def test_intersection(self):
self.assertRaises(TypeError, lambda: self.set & self.other)
self.assertRaises(TypeError, lambda: self.other & self.set)
+ if self.otherIsIterable:
+ self.set.intersection(self.other)
+ else:
+ self.assertRaises(TypeError, self.set.intersection, self.other)
- def test_sym_difference_update(self):
+ def test_sym_difference_update_operator(self):
try:
self.set ^= self.other
except TypeError:
else:
self.fail("expected TypeError")
+ def test_sym_difference_update(self):
+ if self.otherIsIterable:
+ self.set.symmetric_difference_update(self.other)
+ else:
+ self.assertRaises(TypeError,
+ self.set.symmetric_difference_update,
+ self.other)
+
def test_sym_difference(self):
self.assertRaises(TypeError, lambda: self.set ^ self.other)
self.assertRaises(TypeError, lambda: self.other ^ self.set)
+ if self.otherIsIterable:
+ self.set.symmetric_difference(self.other)
+ else:
+ self.assertRaises(TypeError, self.set.symmetric_difference, self.other)
- def test_difference_update(self):
+ def test_difference_update_operator(self):
try:
self.set -= self.other
except TypeError:
else:
self.fail("expected TypeError")
+ def test_difference_update(self):
+ if self.otherIsIterable:
+ self.set.difference_update(self.other)
+ else:
+ self.assertRaises(TypeError,
+ self.set.difference_update,
+ self.other)
+
def test_difference(self):
self.assertRaises(TypeError, lambda: self.set - self.other)
self.assertRaises(TypeError, lambda: self.other - self.set)
-
+ if self.otherIsIterable:
+ self.set.difference(self.other)
+ else:
+ self.assertRaises(TypeError, self.set.difference, self.other)
#------------------------------------------------------------------------------
class TestOnlySetsNumeric(TestOnlySetsInBinaryOps):
def setUp(self):
self.set = Set((1, 2, 3))
self.other = 19
+ self.otherIsIterable = False
#------------------------------------------------------------------------------
def setUp(self):
self.set = Set((1, 2, 3))
self.other = {1:2, 3:4}
+ self.otherIsIterable = True
#------------------------------------------------------------------------------
def setUp(self):
self.set = Set((1, 2, 3))
self.other = operator.add
+ self.otherIsIterable = False
+
+#------------------------------------------------------------------------------
+
+class TestOnlySetsTuple(TestOnlySetsInBinaryOps):
+ def setUp(self):
+ self.set = Set((1, 2, 3))
+ self.other = (2, 4, 6)
+ self.otherIsIterable = True
+
+#------------------------------------------------------------------------------
+
+class TestOnlySetsString(TestOnlySetsInBinaryOps):
+ def setUp(self):
+ self.set = Set((1, 2, 3))
+ self.other = 'abc'
+ self.otherIsIterable = True
+
+#------------------------------------------------------------------------------
+
+class TestOnlySetsGenerator(TestOnlySetsInBinaryOps):
+ def setUp(self):
+ def gen():
+ for i in xrange(0, 10, 2):
+ yield i
+ self.set = Set((1, 2, 3))
+ self.other = gen()
+ self.otherIsIterable = True
#==============================================================================
#==============================================================================
+class TestIdentities(unittest.TestCase):
+ def setUp(self):
+ self.a = Set('abracadabra')
+ self.b = Set('alacazam')
+
+ def test_binopsVsSubsets(self):
+ a, b = self.a, self.b
+ self.assert_(a - b < a)
+ self.assert_(b - a < b)
+ self.assert_(a & b < a)
+ self.assert_(a & b < b)
+ self.assert_(a | b > a)
+ self.assert_(a | b > b)
+ self.assert_(a ^ b < a | b)
+
+ def test_commutativity(self):
+ a, b = self.a, self.b
+ self.assertEqual(a&b, b&a)
+ self.assertEqual(a|b, b|a)
+ self.assertEqual(a^b, b^a)
+ if a != b:
+ self.assertNotEqual(a-b, b-a)
+
+ def test_summations(self):
+ # check that sums of parts equal the whole
+ a, b = self.a, self.b
+ self.assertEqual((a-b)|(a&b)|(b-a), a|b)
+ self.assertEqual((a&b)|(a^b), a|b)
+ self.assertEqual(a|(b-a), a|b)
+ self.assertEqual((a-b)|b, a|b)
+ self.assertEqual((a-b)|(a&b), a)
+ self.assertEqual((b-a)|(a&b), b)
+ self.assertEqual((a-b)|(b-a), a^b)
+
+ def test_exclusion(self):
+ # check that inverse operations show non-overlap
+ a, b, zero = self.a, self.b, Set()
+ self.assertEqual((a-b)&b, zero)
+ self.assertEqual((b-a)&a, zero)
+ self.assertEqual((a&b)&(a^b), zero)
+
+#==============================================================================
+
libreftest = """
Example from the Library Reference: Doc/lib/libsets.tex
... return Base._repr(self, sorted=True)
>>> engineers = Set(['John', 'Jane', 'Jack', 'Janice'])
>>> programmers = Set(['Jack', 'Sam', 'Susan', 'Janice'])
->>> management = Set(['Jane', 'Jack', 'Susan', 'Zack'])
->>> employees = engineers | programmers | management # union
->>> engineering_management = engineers & programmers # intersection
->>> fulltime_management = management - engineers - programmers # difference
+>>> managers = Set(['Jane', 'Jack', 'Susan', 'Zack'])
+>>> employees = engineers | programmers | managers # union
+>>> engineering_management = engineers & managers # intersection
+>>> fulltime_management = managers - engineers - programmers # difference
>>> engineers.add('Marvin')
>>> print engineers
Set(['Jack', 'Jane', 'Janice', 'John', 'Marvin'])
>>> employees.issuperset(engineers) # superset test
False
->>> employees.update(engineers) # update from another set
+>>> employees.union_update(engineers) # update from another set
>>> employees.issuperset(engineers)
True
->>> for group in [engineers, programmers, management, employees]:
+>>> for group in [engineers, programmers, managers, employees]:
... group.discard('Susan') # unconditionally remove element
... print group
...
TestOnlySetsNumeric,
TestOnlySetsDict,
TestOnlySetsOperator,
+ TestOnlySetsTuple,
+ TestOnlySetsString,
+ TestOnlySetsGenerator,
TestCopyingEmpty,
TestCopyingSingleton,
TestCopyingTriple,
TestCopyingTuple,
- TestCopyingNested
+ TestCopyingNested,
+ TestIdentities,
)
test_support.run_doctest(test_sets, verbose)
Library
-------
-- sets.py can now run under Py2.2
+- sets.py now runs under Py2.2. In addition, the argument restrictions
+ for most set methods (but not the operators) have been relaxed to
+ allow any iterable. Also the Set.update() has been deprecated because
+ it duplicates Set.union_update().
- Bug #778964: random.seed() now uses fractional seconds so that
rapid successive, seeding calls will produce different sequences.