mrkeyoor.com_
Fri 07 Aug 19:01 UTC
PyPIUtilsupdated 07 Aug 2026

ordered-set

ordered-set gives you one class, OrderedSet: a mutable set that remembers insertion order and also lets you look items up by position. Internally it keeps a plain list of items plus a dict mapping each item to its index, so membership tests and index lookups are both O(1) and iteration follows insertion order. It implements collections.abc.MutableSet and collections.abc.Sequence at the same time, so set operators (union, intersection, difference) and sequence behavior (indexing, slicing, reversed) work on the same object. It also supports NumPy-style fancy indexing, where you pass a list of indices and get back a list of items, which is why it turns up in data science code as a lightweight stand-in for a two-way vocabulary mapping.

Verdict

Worth the dependency when you genuinely need index lookups on a set, which in practice means vocabulary and id mapping work. If you only want to dedupe a list while keeping order, dict.fromkeys does it in the standard library and you should not install anything.

API stability5/5The public API has not changed since 4.0 in 2020 and the last release was January 2022, so upgrades never break you. That stability is partly because development has stopped.
Docs2/5One README with doctested examples, no documentation site and no API reference. Some docstrings are also wrong: index() says it raises IndexError for a missing item but the implementation raises KeyError.
Maintenance2/5Last PyPI release January 2022, last commit August 2024, an unreleased 4.1.1 sitting on master, and 10 open issues out of 17 open issues and PRs. The code is roughly 500 lines and pure Python, so it keeps working, but do not expect a fix.
Ecosystem4/5Around 9.6M weekly downloads means wheels are on every mirror and the class shows up in plenty of other projects, but it is a single-class library with no plugins, extensions or surrounding tooling.

Use it if

  • You need a two-way mapping between tokens and dense integer ids in a single object: vocab.index(token) and vocab[i] are both O(1), and add() returns the index so interning a token is one line
  • You want set operators and the Sequence protocol on the same value: a | b, a - b, a[3], a[1:4] and reversed(a) without converting between a list and a set every time
  • You need fancy indexing: oset[[0, 2, 5]] and oset.index(['a', 'b']) both accept iterables, including NumPy index arrays, which covers a lot of what people install pandas.Index for
  • Your workload is add-heavy and delete-light, such as building a vocabulary or deduplicating a stream while keeping first-seen order and needing positions afterwards
Skip it if

Setup reality

pip install ordered-set is a pure-Python wheel with no dependencies and no build step, so installation itself is a non-event. The naming is where people lose time: the distribution is ordered-set with a hyphen, the import is from ordered_set import OrderedSet with an underscore, and PyPI carries several similarly named abandoned projects (orderedset, oset, ordered-set-37) that are not this one. The declared floor is Python 3.7 and the classifiers stop at 3.10, so newer interpreters are untested rather than unsupported; being pure Python, they work. Around 9.6M weekly downloads against 230 stars means most installs arrive through some other package's requirements, so before pinning a version check what your build tooling already resolved. The wheel does ship py.typed, so type checkers pick up OrderedSet[str] with no stub package.

Patterns

Deduplicate while keeping first-seen orderdedupe-preserving-order

from ordered_set import OrderedSet

rows = ["b", "a", "b", "c", "a"]
unique = OrderedSet(rows)
list(unique)          # ['b', 'a', 'c']

# standard library equivalent when you only need the list:
list(dict.fromkeys(rows))   # ['b', 'a', 'c']

If this is the whole job, dict.fromkeys is built in and faster. Install OrderedSet only when you also need index lookups or the set operators on the same object.

Look up an item by index and an index by itemlookup-index-and-item

from ordered_set import OrderedSet

letters = OrderedSet("abracadabra")
letters              # OrderedSet(['a', 'b', 'r', 'c', 'd'])
letters.index("r")   # 2
letters[2]           # 'r'
"r" in letters       # True
len(letters)         # 5

Both directions are O(1) because the object holds a list and a dict of item to position. A missing item raises KeyError from index(), not the IndexError the docstring promises, so catch KeyError.

Intern a value and get its id backadd-returns-index

from ordered_set import OrderedSet

letters = OrderedSet("abracadabra")
letters.add("r")     # 2, already present
letters.add("x")     # 5, appended
letters.append("y")  # 6

add() returns the index whether or not the item was new, which is exactly what interning needs. append is a second name for the same method, not a list-style append that would allow duplicates.

Add many items at oncebulk-update

from ordered_set import OrderedSet

oset = OrderedSet([1, 2, 3])
last = oset.update([3, 1, 5, 1, 4])
last    # 4, the index of the last element inserted
oset    # OrderedSet([1, 2, 3, 5, 4])

update returns the index of the final element processed, not a count of new items. Passing a string updates character by character, since str is an iterable.

Index with a list of positions or a list of itemsfancy-indexing

import numpy as np
from ordered_set import OrderedSet

letters = OrderedSet("abracadabra")
letters[[0, 2, 3]]              # ['a', 'r', 'c']
letters.index(["a", "r", "c"])  # [0, 2, 3]
letters[np.array([0, 1])]       # ['a', 'b']

Indexing with an iterable returns a plain list rather than an OrderedSet, because you are allowed to ask for the same position twice. Strings count as single atomic keys here, so letters.index('ab') raises KeyError instead of returning [0, 1].

Union, intersection and difference with order keptset-operations

from ordered_set import OrderedSet

a = OrderedSet("abracadabra")     # a b r c d
a |= OrderedSet("shazam")
a                    # OrderedSet(['a','b','r','c','d','s','h','z','m'])
a & set("aeiou")     # OrderedSet(['a'])
a - {"a", "b"}       # OrderedSet(['r','c','d','s','h','z','m'])
a.symmetric_difference(["r", "q"])

Union keeps the left operand's order and appends new items from the right. Any iterable is accepted on the right, and a bare string is an iterable of characters, so a -= 'abcd' quietly removes four separate letters.

Remove items and pop by positionremove-items

from ordered_set import OrderedSet

oset = OrderedSet(["a", "b", "c", "d"])
oset.discard("b")   # silent if absent
oset.remove("c")    # KeyError if absent
oset.pop()          # 'd', removes the last item
oset.pop(0)         # 'a'
oset.clear()

discard reindexes every item after the removed one, so a single removal is O(n) and removing in a loop is quadratic. Indices you handed out earlier become wrong after any removal, which matters if you were using them as ids.

Slice, copy and reverseslice-and-copy

from ordered_set import OrderedSet

oset = OrderedSet("abcdef")
oset[1:4]              # OrderedSet(['b', 'c', 'd'])
oset[:]                # a copy, same as oset.copy()
list(reversed(oset))   # ['f', 'e', 'd', 'c', 'b', 'a']

A slice returns a new OrderedSet while iterable indexing returns a list, so the return type depends on what you passed. copy() is shallow, meaning the contained objects are shared with the original.

Build a token to id vocabularyvocabulary-mapping

from ordered_set import OrderedSet

vocab = OrderedSet()
ids = [vocab.add(tok) for tok in ["the", "cat", "sat", "the"]]
ids                                # [0, 1, 2, 0]
vocab.get_loc("cat")               # 1
vocab.get_indexer(["sat", "the"])  # [2, 0]
vocab[1]                           # 'cat'

get_loc and get_indexer are aliases for index, added so code written against pandas.Index can swap this in. Ids stay valid only while you never remove anything, since removal shifts every later position.

Know what equality means before asserting on itequality-semantics

from ordered_set import OrderedSet

OrderedSet([1, 2, 3]) == [1, 2, 3]               # True, order checked
OrderedSet([1, 2, 3]) == [3, 2, 1]               # False
OrderedSet([1, 2, 3]) == {3, 2, 1}               # True, order ignored
OrderedSet([1, 2, 3]) == OrderedSet([3, 2, 1])   # False

Comparison against a Sequence is order-sensitive and comparison against a set is not, so two assertions that look equivalent behave differently. An OrderedSet is unhashable, so it cannot go inside another set or act as a dict key.

Annotate an OrderedSet for type checkerstyped-annotation

from typing import Tuple
from ordered_set import OrderedSet

seen: OrderedSet[str] = OrderedSet()
pairs: OrderedSet[Tuple[int, str]] = OrderedSet([(1, "a")])

def unique(items: list[str]) -> OrderedSet[str]:
    return OrderedSet(items)

The class is generic and the wheel ships a py.typed marker, so mypy and pyright resolve the parameter without a stubs package. Note the import is ordered_set with an underscore even though you installed ordered-set with a hyphen.

Alternatives

PackageRegistryPick it when
sortedcontainersPyPIYou want sorted order rather than insertion order, plus indexing, and you delete items often; SortedSet keeps O(log n) behavior on removal.
boltonsPyPIYou want an insertion-ordered indexed set with cheaper deletions (IndexedSet compacts dead slots) inside an actively maintained general utility library.
orderly-setPyPIYou want a maintained drop-in successor with newer Python support and several set variants, as used by deepdiff.