Sets

By the Syncfusion DevTutorials team · Last updated

Unique unordered values: creating sets, add and discard, union, intersection and difference, the whole method list, and frozen sets.

Think about a guest list for a school event.

Each name appears once. Writing a name twice changes nothing — the person is either invited or not. And nobody cares what order the names are in; the only question anyone asks is "is this person on the list?"

That is a set: no duplicates, no order, and a very fast way to ask whether something is in it.

Here is that idea in action — five names go in, and only the distinct ones stay:

Duplicates vanish on their own

invited = {"Asha", "Ravi", "Asha", "Meera", "Ravi"}

print(len(invited))
print(sorted(invited))
print("Asha" in invited)

Five names went in and three came out. The set kept one of each.

A set holds unique values with no order, and answers "is this in here?" very quickly.

What You Will Learn

In this lesson, you will learn:

  • What makes a set different from a list, and when that difference matters
  • How to create one, including the empty-set trap
  • How to add and remove items safely
  • The four set operations, which answer real questions in one line
  • The whole method list, and which methods change the set
  • What a frozen set is, and when you need one

By the end of this lesson, you will be able to create sets, add and remove items safely, use the four set operations to compare groups, pick the right method for each job, and decide when a frozen set is needed.

Python Sets

Introduction to Sets

A set is a collection of unique values with no order.

It is the right tool for three jobs: removing duplicates, asking whether something is present, and comparing two groups.

Creating Sets

There are several ways to make a set, but one of them also solves the common problem of removing duplicates from another collection:

Three ways to make a set

a = {78, 85, 60}
b = set([78, 85, 60])
c = set("Asha")

print(sorted(a))
print(sorted(b))
print(sorted(c))

Set Characteristics

Most of a set's characteristics follow from one idea — a set forgets both duplicates and positions:

CharacteristicWhat it means
Uniqueduplicates are dropped, silently
Unorderedthere is no first item, and no indexing
Changeableyou can add and remove items
Hashable items onlynumbers, text and tuples — not lists
Very fast lookupin does not search item by item

Unique Elements in Sets

Dropping duplicates is half of what a set is. Here it is working on a list:

Duplicates are dropped

marks = [78, 85, 78, 60, 85, 78]

unique = set(marks)

print("original:", marks)
print("unique:  ", sorted(unique))
print(len(marks), "->", len(unique))

Removing duplicates from a list is the single most common use:

Unique, and back to a list

marks = [78, 85, 78, 60]

print(sorted(set(marks)))
print(list(set(marks)) != [])

Note the sorted(). Turning a set back into a list gives no promise about order, so sort it if the order matters.

Unordered Nature of Sets

A set has no positions, so there is no indexing and no slicing.

A set cannot be indexed

marks = {78, 85, 60}

print(marks[0])

Printing a set shows its items in whatever order it happens to store them. That order is not something to rely on — for text it can differ between runs of the program.

The order you see is not promised

subjects = {"Maths", "Science", "English", "History"}

print(subjects)
print(sorted(subjects))

The second line is the one to use when the order matters to a reader.

Mutable Sets

Although a set has no positions, it can still grow and shrink as items are added and removed:

Sets can be changed

marks = {78, 85}

marks.add(60)
marks.discard(78)

print(sorted(marks))

Data Types Supported in Sets

Everything in a set must be hashable, which in practice means unchangeable.

What may go in a set

mixed = {78, 78.5, "Asha", True, None, (1, 2)}

print(len(mixed))
print(sorted(str(item) for item in mixed))

A list cannot go in a set

{[78, 85]}

A list could change after it went in, which would leave the set unable to find it again. Use a tuple instead.

One oddity worth seeing once, because it also applies to dictionary keys:

True and 1 are the same value

print({1, True, 1.0})
print({0, False})
print(len({1, True}))

True equals 1, so the set keeps only one of them.

Creating Sets

The empty set is the one case that does not work the way you would guess.

Using Curly Braces

Curly braces

marks = {78, 85, 60}

print(sorted(marks), type(marks).__name__)

Using the set() Function

set() from other collections

print(sorted(set([78, 85, 78])))
print(sorted(set((78, 85))))
print(sorted(set("hello")))
print(sorted(set({"maths": 78, "science": 85})))

On a dictionary, set() takes the keys.

Empty Sets

This is the trap.

Empty braces are a dictionary, not a set

not_a_set = {}
real_set = set()

print(type(not_a_set).__name__)
print(type(real_set).__name__)

{} was dictionary syntax long before sets existed, so an empty set has to be written set().

Where that bites

collection = {}

collection.add(78)

AttributeError: 'dict' object has no attribute 'add' — the object was a dictionary all along.

Creating Sets from Lists and Tuples

From a list, and back again

marks = [78, 85, 78, 60]

unique = set(marks)
back_to_list = sorted(unique)

print(unique == {60, 78, 85})
print(back_to_list)

Working with Set Elements

Now that you can make a set, here is how to put items in and take them out.

Adding Elements with add()

add() puts in one item. Adding something already there does nothing, with no complaint.

add

marks = {78, 85}

marks.add(60)
marks.add(78)

print(sorted(marks))

Removing Elements with remove()

remove() deletes an item and raises if it is not there.

remove on a missing item

marks = {78, 85}

marks.remove(78)
print(sorted(marks))

marks.remove(99)

Removing Elements with discard()

discard() does the same and says nothing when the item is missing.

discard never complains

marks = {78, 85}

marks.discard(78)
marks.discard(99)

print(sorted(marks))

Use remove() when the item ought to be there and its absence is a bug. Use discard() when you simply want it gone.

pop() removes an arbitrary item — not the first, because there is no first:

pop takes whichever item it likes

marks = {78}

print(marks.pop())
print(marks)

pop on an empty set

set().pop()

Clearing a Set

clear() empties the whole set at once:

clear

marks = {78, 85}

marks.clear()

print(marks, len(marks))

Updating a Set

update() adds everything from another collection.

update

marks = {78, 85}

marks.update([60, 92])
marks.update({45}, (78,))
marks.update("ab")

print(sorted(str(item) for item in marks))

add() puts in one item; update() puts in each item of whatever you give it — including each character of a string, which surprises people.

update("Asha") adds four separate characters, not the word. A string is a collection of characters, and update() takes each item of whatever it is given. Use add("Asha") when you mean the whole word.

Membership Testing

Using the in Operator

This is the question sets were built to answer:

in

invited = {"Asha", "Ravi", "Meera"}

print("Asha" in invited)
print("Ken" in invited)
print("Ken" not in invited)

A list checks items one by one; a set jumps straight to the answer, however large it is.

The same question, two collections

big_list = list(range(50000))
big_set = set(big_list)

print(49999 in big_list)
print(49999 in big_set)
print("Both correct — but the set did far less work")

For a handful of items the difference does not matter. For thousands, checked repeatedly, it is the difference between quick and unusable.

When a program keeps asking "have I seen this before?", build a set once and ask it, rather than searching a list every time. That one change turns a slow loop into a quick one more often than any other single fix.

Set Operations

The four operations are what make sets worth learning. Each answers a question that would otherwise need a loop.

The examples all use these two groups of students:

Two groups to compare

maths = {"Asha", "Ravi", "Meera"}
science = {"Ravi", "Meera", "Ken"}

print("Maths:  ", sorted(maths))
print("Science:", sorted(science))

Union

Everyone in either group.

union

maths = {"Asha", "Ravi", "Meera"}
science = {"Ravi", "Meera", "Ken"}

print(sorted(maths.union(science)))
print(sorted(maths | science))

Intersection

Only those in both.

intersection

maths = {"Asha", "Ravi", "Meera"}
science = {"Ravi", "Meera", "Ken"}

print(sorted(maths.intersection(science)))
print(sorted(maths & science))

Difference

In the first group and not the second. Order matters here.

difference

maths = {"Asha", "Ravi", "Meera"}
science = {"Ravi", "Meera", "Ken"}

print(sorted(maths.difference(science)))
print(sorted(science - maths))

Symmetric Difference

In one group or the other, but not both.

symmetric_difference

maths = {"Asha", "Ravi", "Meera"}
science = {"Ravi", "Meera", "Ken"}

print(sorted(maths.symmetric_difference(science)))
print(sorted(maths ^ science))

All four, side by side:

OperationOperatorMethodAnswers
Union|union()in either
Intersection&intersection()in both
Difference-difference()in the first only
Symmetric difference^symmetric_difference()in one but not both

The methods accept any collection; the operators need both sides to be sets.

An operator needs two sets

marks = {78, 85}

print(sorted(marks.union([60])))

print(marks | [60])

Subset Testing

A subset is a group whose members all appear inside another group:

issubset

everyone = {"Asha", "Ravi", "Meera", "Ken"}
maths = {"Asha", "Ravi"}

print(maths.issubset(everyone))
print(maths <= everyone)
print(everyone.issubset(maths))

Superset Testing

A superset is the opposite relationship: it contains every item from another set:

issuperset

everyone = {"Asha", "Ravi", "Meera", "Ken"}
maths = {"Asha", "Ravi"}

print(everyone.issuperset(maths))
print(everyone >= maths)

A set is both a subset and a superset of itself:

A set contains itself

marks = {78, 85}

print(marks.issubset(marks))
print(marks.issuperset(marks))
print(marks < marks)

< means "a subset, and not equal", which is why the last line is False.

Disjoint Sets

Two sets are disjoint when they share nothing.

isdisjoint

maths = {"Asha", "Ravi"}
music = {"Ken", "Priya"}
science = {"Ravi", "Ken"}

print(maths.isdisjoint(music))
print(maths.isdisjoint(science))

a.isdisjoint(b) says the same as not (a & b), and says it more clearly.

Set Methods

You have now met the methods one at a time. Here is the full list, starting with the ones that change the set:

The methods that change the set

marks = {78}

marks.add(85)
print("add:     ", sorted(marks))

marks.update([60, 92])
print("update:  ", sorted(marks))

marks.remove(92)
print("remove:  ", sorted(marks))

marks.discard(99)
print("discard: ", sorted(marks))

copy_of = marks.copy()
marks.clear()
print("clear:   ", marks, "copy still:", sorted(copy_of))

Each operation also has an in-place version that changes the set instead of returning a new one:

The in-place versions

maths = {"Asha", "Ravi"}
science = {"Ravi", "Ken"}

both = maths.copy()
both.update(science)
print("update (union):", sorted(both))

shared = maths.copy()
shared.intersection_update(science)
print("intersection_update:", sorted(shared))

only_maths = maths.copy()
only_maths.difference_update(science)
print("difference_update:", sorted(only_maths))
MethodWhat it doesChanges the set?
add(x)adds one itemyes
update(xs)adds each item of a collectionyes
remove(x)deletes x, raises if missingyes
discard(x)deletes x, silent if missingyes
pop()removes an arbitrary itemyes
clear()empties the setyes
copy()a shallow copyno
union(xs)items in eitherno
intersection(xs)items in bothno
difference(xs)items only in this oneno
symmetric_difference(xs)items in one but not bothno

Frozen Sets

One more kind of set exists, and it solves the problem you just saw — a set cannot go inside another set.

Introduction to Frozen Sets

A frozen set is a set that cannot be changed — the tuple to a set's list.

Creating Frozen Sets with frozenset()

frozenset

subjects = frozenset(["Maths", "Science"])

print(sorted(subjects))
print(len(subjects), "Maths" in subjects)

A frozen set refuses to change

subjects = frozenset(["Maths"])

subjects.add("Science")

Mutable vs Immutable Sets

Everything that only reads the set still works:

Everything that reads still works

a = frozenset([78, 85, 60])
b = frozenset([85, 92])

print(sorted(a | b))
print(sorted(a & b))
print(a.issubset({78, 85, 60, 92}))
print(type(a | b).__name__)

Every operation that returns something works. Only the ones that change it are missing.

Use Cases of Frozen Sets

The main one: a frozen set can go inside another set, or be a dictionary key. An ordinary set cannot.

A set cannot contain a set

{{78, 85}}

A frozen set can

groups = {frozenset(["Asha", "Ravi"]), frozenset(["Meera"])}

print(len(groups))

timetable = {
    frozenset(["Monday", "Wednesday"]): "Maths",
    frozenset(["Tuesday"]): "Science",
}

print(timetable[frozenset(["Wednesday", "Monday"])])

The last line matters: the key was looked up with the days in a different order and still found, because a set has no order.

The other use is a constant that nobody can change by accident:

A constant collection

SCHOOL_DAYS = frozenset(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"])

def try_to_add(days):
    try:
        days.add("Saturday")
        return "changed it"
    except AttributeError:
        return "could not change it"

print(try_to_add(SCHOOL_DAYS))
print(len(SCHOOL_DAYS))

Built-in Functions with Sets

The built-ins that work on a set

marks = {78, 85, 60, 92}

print("len:   ", len(marks))
print("max:   ", max(marks))
print("min:   ", min(marks))
print("sum:   ", sum(marks))
print("sorted:", sorted(marks))
print("set:   ", sorted(set([78, 78, 85])))

sorted() is the usual way to show a set to a person, because it fixes the order.

zip on sets is a bad idea

subjects = ["Maths", "Science"]
marks = [78, 85]

print(list(zip(subjects, marks)))
print(dict(zip(subjects, marks)))

zip() pairs things up by position, and a set has no positions — so zipping two sets pairs values arbitrarily. Use lists or tuples when order matters.

Common mistakes

Before moving on, here are some of the mistakes beginners make with sets.

Each row is one mistake. The middle column is what you will actually see on screen.

MistakeWhat you seeWhy it happensFix
{} for an empty setAttributeError: 'dict' object has no attribute 'add'{} is an empty dictionaryset()
Indexing a setTypeError: 'set' object is not subscriptableA set has no positionsConvert with sorted() or list()
Relying on the printed orderDifferent order on a different runA set's order is not promisedsorted() before printing
remove() on a missing itemKeyErrorremove() raises; discard() does notUse discard(), or check with in
A list inside a setTypeError: unhashable type: 'list'Items must be unchangeableUse a tuple
A set inside a setTypeError: unhashable type: 'set'A set can change, so it cannot be hashedUse frozenset
add() where update() was meantThe whole collection added as one itemadd adds exactly one thingupdate to add each item
update() with a stringEach character added separatelyA string is a collection of charactersadd("Asha") for the whole word
Expecting duplicates to be reportedThey vanish silentlyDropping duplicates is the pointCompare len() before and after
An operator with a list on one sideTypeError: unsupported operand type(s)Operators need two setsUse the method, which accepts any collection
Expecting pop() to take the first itemSome other item is removedThere is no first itemUse a list if order matters

What to Remember

  • A set holds unique values with no order, and answers in very quickly.
  • Duplicates are dropped silently, which is what makes set(marks) the usual way to find unique values.
  • {} is an empty dictionary. An empty set is set().
  • A set has no positions, so no indexing and no slicing.
  • Never rely on the printed order — use sorted() whenever a reader will see it.
  • Items must be hashable: numbers, text and tuples, never lists or sets.
  • add() adds one item; update() adds each item of a collection, including each character of a string.
  • remove() raises when the item is missing; discard() does not.
  • | union, & intersection, - difference, ^ symmetric difference — and the methods accept any collection while the operators need two sets.
  • A frozen set cannot change, so it can be a dictionary key or go inside another set.

Next: Dictionary