8.2.5. Creating sets#
A set is created using curly braces {} with comma-separated values, or by using the set() constructor.
Unlike lists, sets are unordered and do not allow duplicates - if you add the same value twice, it only appears once. This makes sets useful for storing unique items. One important gotcha: an empty {} creates a dict, not a set - you must use set() for an empty set.
import sys
from pathlib import Path
# Find project root by looking for _config.yml
current = Path.cwd()
for parent in [current, *current.parents]:
if (parent / '_config.yml').exists():
project_root = parent
break
else:
project_root = Path.cwd().parent.parent
# Add project root to path
sys.path.insert(0, str(project_root))
# Import shared teaching helpers and cell magics
from shared import thinkpython, diagram, jupyturtle, structshape
from shared.download import download
s = {1, 2, 3, 3} # duplicate 3 is ignored
print(s) # {1, 2, 3}
s = set([1, 2, 2, 3]) # from a list
print(s) # {1, 2, 3}
s = set() # empty set
d = {} # this is a dict, not a set!
{1, 2, 3}
{1, 2, 3}
set elements must be hashable. Since sets use hashing internally to enforce uniqueness and enable fast lookup, all elements must be hashable. This means you can store integers, floats, strings, and tuples in a set, but not lists or dicts.
%%expect TypeError
# Valid
s = {1, "hello", (1, 2)}
# Invalid
s = {[1, 2], [3, 4]} # TypeError: unhashable type: 'list'
TypeError: unhashable type: 'list'
Similarly, a tuple is hashable only if all of its elements are hashable.
print(hash((1, 2))) # ok
print(hash((1, (2, 3)))) # ok
try:
hash((1, [2, 3])) # list inside tuple => unhashable
except TypeError as e:
print(e)
-3550055125485641917
7267574591690527098
unhashable type: 'list'
8.2.5.1. Accessing Elements#
Sets have no indexing or positional access. Because sets are unordered, there’s no concept of “first” or “second” element, so index access makes no sense.
Compared with list and dictionary, dict is the only one with a built-in safe getter. A list requires a guard clause.
List |
Dict |
Set |
|
|---|---|---|---|
Access |
by index |
by key |
no direct access |
Safe get |
|
|
|
Built-in safe method |
none |
|
none |
%%expect IndexError
d = {'x': 1, 'y': 2}
d.get('z', 'not found') # 'not found' — no error
l = [10, 20, 30]
l[5] # IndexError! ### uncomment to see the error
IndexError: list index out of range
For list, a safe way to handle a possible out-of-range error is to use a guard clause.
l = [10, 20, 30]
result = l[5] if 5 < len(l) else None # None — safe
print(result) # None
None
To work with elements in a set, you can:
Method |
Example |
Notes |
|---|---|---|
Membership check |
|
True/False only, no error |
Iterate |
|
no guaranteed order |
Convert to list |
|
order not guaranteed |
|
|
returns an arbitrary element |
### check membership
if "apple" in s:
print("found")
### iterate over a set
for element in s:
print(element)
1
(1, 2)
hello
### conversion to list
l = list(s)
print(l)
[1, (1, 2), 'hello']
### s.pop() removes and returns an arbitrary element from the set. If the set is empty, it raises a KeyError.
s = {1, 2, 3}
s.pop()
1