Python Set Comprehension Patterns
Set comprehensions are the underused sibling of list and dict comprehensions. They build deduplicated collections in one line and excel at unique-by-key extraction, set algebra, and quick membership filters. This snippet covers the basic uniqueness pattern, the unique-by-projection form for objects, and set algebra (intersection, difference) expressed as comprehensions.
1,064 views
20
words = ['Apple', 'apple', 'BANANA', 'banana', 'cherry']
lowercased_unique = {w.lower() for w in words}
print(sorted(lowercased_unique)) # ['apple', 'banana', 'cherry']
lengths = {len(w) for w in words}
print(sorted(lengths)) # [5, 6]
first_letters = {w[0].upper() for w in words}
print(sorted(first_letters)) # ['A', 'B', 'C']A set comprehension uses curly braces and produces a set, dropping duplicates as it goes. This is the cleanest way to compute 'how many distinct lengths', 'unique first letters', or 'normalised forms' from a list. The output is unordered; sort it before printing or comparing if order matters in your test. Time complexity is O(n) for n inputs because each insertion into a set is amortised O(1).
users = [
{'id': 1, 'role': 'admin'},
{'id': 2, 'role': 'member'},
{'id': 1, 'role': 'admin'},
{'id': 3, 'role': 'member'},
]
unique_ids = {u['id'] for u in users}
print(sorted(unique_ids)) # [1, 2, 3]
seen_roles = {u['role'] for u in users}
print(sorted(seen_roles)) # ['admin', 'member']
# Unique objects by key (keep first seen)
seen = set()
first_per_id = []
for u in users:
if u['id'] in seen:
continue
seen.add(u['id'])
first_per_id.append(u)
print(first_per_id)When you only need the unique projections (the IDs, the roles, the dates), a set comprehension is the right tool. When you need the unique objects themselves, a set comprehension cannot help directly because dicts are not hashable. The sentinel pattern (seen set plus an explicit loop) is the canonical alternative and runs in O(n) with O(n) extra space. Hashable replacement values (a tuple of fields, a frozenset of items) can sidestep the loop, but the explicit pattern is usually clearer.
tags_a = {'red', 'blue', 'green'}
tags_b = {'blue', 'yellow'}
in_both = {t for t in tags_a if t in tags_b}
print(in_both) # {'blue'}
only_in_a = {t for t in tags_a if t not in tags_b}
print(only_in_a) # {'red', 'green'}
# Equivalent operator forms (faster, idiomatic)
print(tags_a & tags_b) # {'blue'}
print(tags_a - tags_b) # {'red', 'green'}
print(tags_a | tags_b) # {'red', 'blue', 'green', 'yellow'}Intersection, difference, and union can all be expressed as set comprehensions, but the operator forms (&, -, |) are shorter, idiomatic, and dispatched to the underlying C implementation, so they are also faster. Reach for the comprehension form only when the predicate is more complex than 'is in the other set' (case-insensitive comparison, normalised match). The same operators also work with frozenset, which gives you immutable, hashable sets when you need to use them as dict keys.
