The set type brings the practical expressiveness of set theory to Python. It has a very rich API overall, but lacks a couple of fundamental features. For one, sets are not ordered. On top of this, sets are not indexable, i.e, my_set[8] will raise an TypeError. The IndexedSet type remedies both of these issues without compromising on the excellent complexity characteristics of Python's built-in set implementation.



IndexedSet is a collections.MutableSet that maintains



Given a set, convert it to a complement set.

class IndexedSet(other=None)#


IndexedSet is a collections.MutableSet that maintains insertion order and uniqueness of inserted elements. It's a hybrid type, mostly like an OrderedSet, but also list-like, in that it supports indexing and slicing.


other (iterable) -- An optional iterable used to initialize the set.

>>> x = IndexedSet(list(range(4)) + list(range(8)))
>>> x
IndexedSet([0, 1, 2, 3, 4, 5, 6, 7])
>>> x - set(range(2))
IndexedSet([2, 3, 4, 5, 6, 7])
>>> x[-1]
>>> fcr = IndexedSet('')
>>> ''.join(fcr[:fcr.index('.')])

Standard set operators and interoperation with set are all supported:

>>> fcr & set('')
IndexedSet(['c', 'd', 'o', '.', 'm'])

As you can see, the IndexedSet is almost like a UniqueList, retaining only one copy of a given value, in the order it was first added. For the curious, the reason why IndexedSet does not support setting items based on index (i.e, __setitem__()), consider the following dilemma:

my_indexed_set = [A, B, C, D]
my_indexed_set[2] = A

At this point, a set requires only one A, but a list would overwrite C. Overwriting C would change the length of the list, meaning that my_indexed_set[2] would not be A, as expected with a list, but rather D. So, no __setitem__().

Otherwise, the API strives to be as complete a union of the list and set APIs as possible.

property _dead_index_count#
_add_dead(start, stop=None)#

Return repr(self).


Return self==value.

classmethod from_iterable(it)#

from_iterable(it) -> create a set from an iterable


add(item) -> add item to the set


remove(item) -> remove item from the set, raises if not present


discard(item) -> discard item from the set (does not raise)


clear() -> empty the set


isdisjoint(other) -> return True if no overlap with other


issubset(other) -> return True if other contains this set


issuperset(other) -> return True if set contains other


union(*others) -> return a new set containing this set and others


iter_intersection(*others) -> iterate over elements also in others


intersection(*others) -> get a set with overlap of this and others


iter_difference(*others) -> iterate over elements not in others


difference(*others) -> get a new set with elements not in others


symmetric_difference(*others) -> XOR set of this and others


update(*others) -> add values from one or more iterables


intersection_update(*others) -> discard self.difference(*others)


difference_update(*others) -> discard self.intersection(*others)


symmetric_difference_update(other) -> in-place XOR with other

iter_slice(start, stop, step=None)#

iterate over a slice of the set


pop(index) -> remove the item at a given index (-1 by default)


count(val) -> count number of instances of value (0 or 1)


reverse() -> reverse the contents of the set in-place


sort() -> sort the contents of the set in-place


index(val) -> get the index of a value, raises if not present


Given a set, convert it to a complement set.

Whereas a set keeps track of what it contains, a complement set keeps track of what it does not contain. For example, look what happens when we intersect a normal set with a complement set:

>>> list(set(range(5)) & complement(set([2, 3])))

[0, 1, 4]

We get the everything in the left that wasn't in the right, because intersecting with a complement is the same as subtracting a normal set.


wrapped (set) -- A set or any other iterable which should be turned into a complement set.

All set methods and operators are supported by complement sets, between other complement()-wrapped sets and/or regular set objects.

Because a complement set only tracks what elements are not in the set, functionality based on set contents is unavailable: len(), iter() (and for loops), and .pop(). But a complement set can always be turned back into a regular set by complementing it again:

>>> s = set(range(5))
>>> complement(complement(s)) == s


An empty complement set corresponds to the concept of a universal set from mathematics.

Complement sets by example#

Many uses of sets can be expressed more simply by using a complement. Rather than trying to work out in your head the proper way to invert an expression, you can just throw a complement on the set. Consider this example of a name filter:

>>> class NamesFilter(object):
...    def __init__(self, allowed):
...        self._allowed = allowed
...    def filter(self, names):
...        return [name for name in names if name in self._allowed]
>>> NamesFilter(set(['alice', 'bob'])).filter(['alice', 'bob', 'carol'])
['alice', 'bob']

What if we want to just express "let all the names through"?

We could try to enumerate all of the expected names:

``NamesFilter({'alice', 'bob', 'carol'})``

But this is very brittle -- what if at some point over this object is changed to filter ['alice', 'bob', 'carol', 'dan']?

Even worse, what about the poor programmer who next works on this piece of code? They cannot tell whether the purpose of the large allowed set was "allow everything", or if 'dan' was excluded for some subtle reason.

A complement set lets the programmer intention be expressed succinctly and directly:


Not only is this code short and robust, it is easy to understand the intention.