Sets in Python are a built-in collection type designed to store unique values and perform fast membership checks.
A set is an unordered collection of unique values. Unlike a list, a set automatically throws away duplicates and doesn’t remember any particular order for its items – what it offers instead is extremely fast membership testing and a full set of mathematical operations borrowed directly from set theory: union, intersection, difference, and symmetric difference.
fruits = {'apple', 'banana', 'cherry', 'apple'}
print(fruits) # {'apple', 'banana', 'cherry'} – the duplicate is gone
print(len(fruits)) # 3
A set is written with curly braces, the same as a dictionary, but holds bare values instead of key-value pairs. There’s one important exception to remember: {} on its own creates an empty dictionary, not an empty set, because Python reserves the bare curly braces for dictionaries by default. An empty set has to be created with the set() function instead.
empty_dict = {} # this is a dictionary
empty_set = set() # this is an empty set
numbers = set([1, 2, 2, 3, 3, 3])
print(numbers) # {1, 2, 3}
fruits = {'apple', 'banana'}
fruits.add('cherry')
fruits.discard('banana')
print(fruits) # {'apple', 'cherry'}
print('apple' in fruits) # True
discard() removes an item if it’s present and does nothing if it isn’t; the closely related remove() does the same job but raises a KeyError if the item is missing, so discard() is the safer default unless a missing item genuinely signals a bug worth catching. Checking membership with in is where sets truly distinguish themselves from lists: because a set is built internally as a hash table rather than a plain sequence, checking whether a value is present takes roughly the same tiny amount of time no matter how large the set is, while checking membership in a list gets slower as the list grows, since Python may have to scan every single element.
The union of two sets contains every element that appears in either one, with duplicates automatically collapsed. Python offers both a method and an operator for it.
a = {1, 2, 3}
b = {3, 4, 5}
print(a.union(b)) # {1, 2, 3, 4, 5}
print(a | b) # {1, 2, 3, 4, 5} – same result
The intersection keeps only the elements present in both sets at once.
a = {1, 2, 3}
b = {3, 4, 5}
print(a.intersection(b)) # {3}
print(a & b) # {3} – same result
The difference keeps whatever is in the first set but not in the second – and because of that, unlike union and intersection, the order you write it in changes the answer.
a = {1, 2, 3}
b = {3, 4, 5}
print(a.difference(b)) # {1, 2} – in a, not in b
print(b.difference(a)) # {4, 5} – in b, not in a
print(a - b) # {1, 2} – same as a.difference(b)
The symmetric difference keeps everything that’s in exactly one of the two sets, excluding whatever overlaps between them – effectively the union with the intersection removed.
a = {1, 2, 3}
b = {3, 4, 5}
print(a.symmetric_difference(b)) # {1, 2, 4, 5}
print(a ^ b) # {1, 2, 4, 5} – same result
These four operations turn tasks that would otherwise require nested nested loops and manual bookkeeping – find everyone who appears in both lists, find everyone who’s only in one, and so on – into a single, direct expression that says exactly what it means.
Because a set can never contain duplicates, converting a list to a set and back is a common, quick way to strip repeated values out of a collection.
names = ['Ali', 'Sara', 'Ali', 'Bilal', 'Sara'] unique_names = list(set(names)) print(unique_names) # order not guaranteed, but no duplicates
Worth remembering: this trick only works cleanly if the original order doesn’t matter, since sets don’t preserve the sequence items were added in. If you need to remove duplicates while keeping the original order intact, a small loop that checks membership in a set (fast) while still building an ordinary list (ordered) does the job instead.
Just like list comprehensions and dictionary comprehensions, a set can be built in one line with a comprehension – swap the square brackets for curly braces.
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_squares = {n * n for n in numbers if n % 2 == 0}
print(even_squares) # {4, 16, 36, 64, 100}
As with any set, the result has no guaranteed order and automatically drops any duplicate values the expression happens to produce.
A set itself can be changed after creation – items can be added and removed freely – but every individual element inside it must be an immutable type: numbers, strings, and tuples are all allowed, but a list can never be placed inside a set, for exactly the same underlying reason a list can never be used as a dictionary key – Python needs every element’s value to stay fixed once it’s been hashed and filed into the set’s internal structure.
valid = {1, 'two', (3, 4)} # fine – number, string, tuple
invalid = {1, [2, 3]} # raises TypeError – a list can't go inside a set
If you need an immutable, hashable set-like object – one that could itself sit inside another set, for instance – Python provides frozenset(), which behaves exactly like a set but, once created, can’t be changed.
No Comments