Python Sets: Unique Unordered Collections
A set stores unique values with no order and answers membership questions almost instantly. That trade - lose order, gain speed and uniqueness - is the whole point.
- Basics
- Data Types
- Operators
- Strings
- Control Flow
- Lists
- Tuples
- Sets
- Dictionaries
- Comprehensions
- Functions
- Advanced Functions
- Recursion
- Exception Handling
- File Handling
- Modules
- Standard Library
- OOP
- Advanced OOP
- Iterators and Generators
- Decorators
- Context Managers
- Descriptors and Dataclasses
- Python Internals
- Concurrency
- Regular Expressions
- Serialization
- Command Line Python
- Testing and Debugging
- Type Hints
- Performance
- Python Security
- DSA with Python
Creating a set
numbers = {1, 2, 3}
names = {"Meera", "Arun"}
empty = set() # NOT {} - that is an empty dictionary
print(type({})) # <class 'dict'>
print(type(set())) # <class 'set'>
from_list = set([1, 2, 2, 3, 3, 3])
print(from_list) # {1, 2, 3} - duplicates removed
from_string = set("mississippi")
print(from_string) # {'m', 'i', 's', 'p'} in some orderThe three defining properties
- Unique. Adding a value that is already present does nothing.
- Unordered. There are no positions, so no indexing and no slicing.
- Fast membership.
x in sis roughly constant time regardless of size.
s = {3, 1, 2}
print(len(s)) # 3
print(2 in s) # True
# print(s[0]) # TypeError: 'set' object is not subscriptable
s.add(3)
print(len(s)) # 3 - unchanged, 3 was already thereA set printing in sorted order for small integers is a coincidence of how they hash. Never rely on it. If order matters, use a list, or call sorted() when you display.Only hashable values
valid = {1, "two", (3, 4), True, None, 3.5}
# invalid = {[1, 2]} # TypeError: unhashable type: 'list'
# invalid = {{"a": 1}} # TypeError: unhashable type: 'dict'
pairs = {(0, 0), (1, 2)} # tuples are fine
print((1, 2) in pairs) # TrueSet membership is implemented with hashing, and a value that could change would break the lookup. That is why only immutable values are allowed.
An equality subtlety
print({1, True}) # {1} - True equals 1, so one of them is dropped
print({0, False, ""}) # {0, ''} - 0 and False collide, "" does notAdding and removing
s = {1, 2}
s.add(3) # add ONE item
print(s) # {1, 2, 3}
s.update([4, 5]) # add every item of an iterable
s.update("ab", {6}) # several iterables at once
print(s) # {1, 2, 3, 4, 5, 6, 'a', 'b'}
s.remove(1) # KeyError if not present
s.discard(99) # no error if not present
print(s)
value = s.pop() # removes and returns an ARBITRARY item
print(value)
s.clear()
print(s) # set()| Missing value | |
|---|---|
remove(x) | Raises KeyError |
discard(x) | Does nothing |
Why membership is fast
import time
big_list = list(range(1_000_000))
big_set = set(big_list)
start = time.perf_counter()
print(999_999 in big_list)
print("list:", time.perf_counter() - start)
start = time.perf_counter()
print(999_999 in big_set)
print("set: ", time.perf_counter() - start)The list must compare against up to a million items. The set computes one hash and looks in one place. For a single check the difference is invisible; inside a loop that runs a million times it is the difference between minutes and milliseconds.
blocked = ["user1", "user2", "user3"] # if this grows large...
requests = ["user7", "user2", "user9"]
blocked = set(blocked) # ...convert ONCE, before the loop
allowed = [r for r in requests if r not in blocked]
print(allowed)frozenset
immutable = frozenset([1, 2, 3])
print(2 in immutable) # True
# immutable.add(4) # AttributeError
# A frozenset IS hashable, so it can be a key or live inside a set
groups = {frozenset({"a", "b"}), frozenset({"c"})}
print(len(groups)) # 2
lookup = {frozenset({1, 2}): "pair one-two"}
print(lookup[frozenset({2, 1})]) # order does not matterIterating and converting
s = {3, 1, 2}
for value in s: # order is not guaranteed
print(value, end=" ")
print()
for value in sorted(s): # deterministic
print(value, end=" ")
print()
print(list(s))
print(sum(s), min(s), max(s), len(s))Set comprehensions
words = ["apple", "banana", "avocado", "cherry"]
initials = {w[0] for w in words}
print(initials) # {'a', 'b', 'c'}
squares = {n * n for n in range(-3, 4)}
print(squares) # {0, 1, 4, 9} - duplicates collapseCommon mistakes
- Writing
{}for an empty set. That is a dictionary. - Trying to index or slice a set.
- Putting a list inside a set.
- Relying on the order a set prints in.
- Using
removewherediscardwas safer. - Converting to a set to deduplicate when the original order mattered.
Best practices
- Use a set when you need uniqueness or repeated membership testing.
- Convert a lookup list to a set once, outside the loop that uses it.
- Use
discardwhen absence is acceptable. - Use
sorted(s)whenever a set is displayed. - Use
dict.fromkeysinstead of a set when deduplicating must preserve order.
Practice
- Count the distinct characters in a sentence, ignoring case and spaces.
- Explain why
{1, True, 1.0}has one element. - Deduplicate a list twice: once with a set and once preserving order, and compare the results.
- Store a collection of coordinate pairs in a set and test membership.
- Explain when a
frozensetis required rather than a set.
Conclusion
A set gives up order and indexing in exchange for guaranteed uniqueness and near instant membership tests. Reach for one whenever you find yourself asking "have I seen this before?" inside a loop.