Dictionary in Python

By the Syncfusion DevTutorials team · Last updated

Key-value pairs: key rules, get() against square brackets, adding and removing, looping over keys, values and items, and every dictionary method.

Think about the contacts on a phone.

You do not scroll to position 47 to find someone. You type a name and the number appears. The name is the way in; the number is what you wanted.

A dictionary works exactly like that: you look things up by a key instead of by a position.

Here is that idea in action — looking up a mark by subject name:

Looking up by name instead of position

marks = {"maths": 78, "science": 85, "english": 60}

print(marks["maths"])
print(len(marks))
print("science" in marks)

A list would make you remember that maths was item 0. A dictionary lets you ask for it by name.

A dictionary stores pairs: a key you look things up by, and the value you get back.

What You Will Learn

In this lesson, you will learn:

  • What a dictionary is, and when it beats a list
  • Which values may be used as keys, and why
  • How to read a value safely when the key may be missing
  • How to add, change and remove pairs
  • How to loop over keys, values, or both
  • Every dictionary method, and the built-ins that work on one

By the end of this lesson, you will be able to create dictionaries, choose safe ways to read values that may be missing, add, change and remove pairs, loop over keys, values or both, and pick the right dictionary method for each job.

Python Dictionaries

Introduction to Dictionaries

A dictionary holds key-value pairs. Each key appears once and leads to one value.

Other languages call this a map, a hash or an associative array. It is the same idea.

Creating Dictionaries

Python gives several ways to create the same dictionary. Which one you choose usually depends on where the data is coming from.

Four ways to make a dictionary

a = {"maths": 78, "science": 85}
b = dict(maths=78, science=85)
c = dict([("maths", 78), ("science", 85)])
d = dict(zip(["maths", "science"], [78, 85]))

print(a)
print(a == b == c == d)

The last form is common: two lists zipped into a dictionary.

Dictionary Characteristics

Most of these follow from the idea of looking up by key:

CharacteristicWhat it means
Key-value pairseach key leads to one value
Unique keysa repeated key replaces the earlier one
Changeableyou can add, change and remove pairs
Orderedpairs stay in the order they were added
Fast lookupfinding a key does not search pair by pair
Hashable keys onlyvalues may be anything

Insertion order has been guaranteed since Python 3.7. Before that, dictionaries were unordered, which is why older material says so.

Pairs stay in the order they were added

marks = {}

marks["science"] = 85
marks["maths"] = 78
marks["english"] = 60

print(marks)
print(list(marks))

Key-Value Pairs

Every dictionary has two halves — the keys and the values — and you can ask for either:

The two halves

student = {"name": "Asha", "age": 13, "marks": [78, 85]}

print(list(student.keys()))
print(list(student.values()))
print(list(student.items()))

Mutable Nature of Dictionaries

Pairs can be changed and removed after the dictionary is made:

Changing a dictionary

marks = {"maths": 78}

marks["maths"] = 90
marks["science"] = 85
del marks["maths"]

print(marks)

Unique Keys

A repeated key does not make a second entry — it replaces the first.

The last value for a key wins

marks = {"maths": 78, "science": 85, "maths": 90}

print(marks)
print(len(marks))

No error, no warning. The first 78 is simply gone.

Dictionary Use Cases

A dictionary fits whenever data has a natural name rather than a position.

The same data, two shapes

as_list = ["Asha", 13, 78]
as_dict = {"name": "Asha", "age": 13, "marks": 78}

print(as_list[2])
print(as_dict["marks"])

as_list[2] means nothing to a reader. as_dict["marks"] explains itself, and still works if another field is added at the front.

Counting a list of values is the classic use:

Counting things — the classic use

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

counts = {}
for mark in marks:
    counts[mark] = counts.get(mark, 0) + 1

print(counts)

Dictionary Keys

Now for the rules about what may be a key. They explain half of what follows.

Key Rules

A key must be hashable, which in practice means unchangeable. Values have no restrictions at all.

Immutable Keys

Many built-in Python types can be used as keys. The important rule is that the key itself must not be changeable after it is created.

What may be a key

mixed = {
    "text": 1,
    78: 2,
    78.5: 3,
    True: 4,
    ("Maths", 1): 5,
    None: 6,
}

print(len(mixed))
print(mixed[("Maths", 1)])

A list cannot be a key

{[1, 2]: "no"}

A list could change after being used as a key, and the dictionary would never find it again.

Unique Keys

A dictionary can never contain the same key twice. Assigning to an existing key replaces its value instead.

Assigning to an existing key replaces it

marks = {"maths": 78}

marks["maths"] = 90

print(marks, len(marks))

Valid and Invalid Keys

The full list of what may be a key:

KeyAllowed?Why
"maths"yestext cannot change
78yesa number cannot change
(1, 2)yesa tuple of unchangeable items
Noneyesthere is only one None
[1, 2]noa list can change
{1, 2}noa set can change
(1, [2])nothe tuple holds a list

That last row is the subtle one — a tuple is allowed, but only if everything inside it is allowed:

A tuple is only a valid key if its items are

{(1, 2): "fine"}

{(1, [2]): "not fine"}

Duplicate Key Behaviour

Two keys that are equal count as one, even when they look different:

True, 1 and 1.0 are one key

d = {1: "one", True: "true", 1.0: "float"}

print(d)
print(len(d))

All three are equal to each other, so there is one entry. The key stayed as the first one written, and the value is the last one assigned — which reads as nonsense unless you know the rule.

Keys are matched by equality, not by appearance. 1, 1.0 and True are all equal, so they are one key. The same catches 0 and False. It only bites when a dictionary mixes numbers and yes-or-no values, and then it bites hard.

Creating Dictionaries

Because {} means an empty dictionary, and sets use {...} too, this is worth a careful look.

Using Curly Braces

The most common way to create a dictionary is with curly braces and key-value pairs separated by colons.

Curly braces

marks = {"maths": 78, "science": 85}
empty = {}

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

Unlike sets, {} really is an empty dictionary.

Using the dict() Function

The dict() function can also build dictionaries from several different kinds of data.

dict()

print(dict(maths=78, science=85))
print(dict([("maths", 78)]))
print(dict({"maths": 78}))

The keyword form only works for keys that are valid Python names — dict(2nd=1) is a syntax error, and {"2nd": 1} is fine.

Empty Dictionaries

A dictionary that starts empty and gets filled as the program runs is a common shape:

Starting empty and filling

report = {}

report["name"] = "Asha"
report["marks"] = [78, 85]

print(report)

Creating Dictionaries with Different Data Types

Values can be anything, including other dictionaries.

A nested dictionary

school = {
    "Asha": {"maths": 78, "science": 85},
    "Ravi": {"maths": 60, "science": 92},
}

print(school["Asha"])
print(school["Asha"]["maths"])

for name, subjects in school.items():
    total = sum(subjects.values())
    print(name, "total:", total)

This shape — a dictionary of dictionaries — is how most real data arrives, including JSON from a web service.

Accessing Dictionary Items

Now that the dictionary exists, here is how to read values out of it.

Using Square Brackets

The simplest way to read a value is to place its key inside square brackets.

Square brackets

marks = {"maths": 78, "science": 85}

print(marks["maths"])

Handling Missing Keys

Looking up a key that does not exist is one of the most common dictionary mistakes. Python reports it immediately.

A missing key raises

marks = {"maths": 78}

print(marks["history"])

KeyError names the key that was not found.

Using the get() Method

get() returns None instead of raising, and lets you choose a different fallback.

get

marks = {"maths": 78}

print(marks.get("maths"))
print(marks.get("history"))
print(marks.get("history", 0))
print(marks.get("history", "not taken"))

Three ways to cope with a missing key

marks = {"maths": 78}

if "history" in marks:
    print(marks["history"])
else:
    print("checked first:", 0)

print("get with a default:", marks.get("history", 0))

try:
    print(marks["history"])
except KeyError:
    print("caught the error")

Use [] when the key must be there and its absence is a bug. Use get() when a missing key is normal.

Why get() makes counting short

marks = [78, 85, 78]

counts = {}
for mark in marks:
    counts[mark] = counts.get(mark, 0) + 1

print(counts)

Without get(), every count needs an if to handle the first time a value appears.

Modifying Dictionary Items

Reading is one half. Here is how to change what is stored.

Updating Existing Values

If a key already exists, assigning to it replaces the old value with a new one.

Changing a value

marks = {"maths": 78}

marks["maths"] = 90
marks["maths"] += 5

print(marks)

Adding New Key-Value Pairs

A dictionary grows automatically when you assign to a key that does not already exist.

Adding a pair

marks = {"maths": 78}

marks["science"] = 85

print(marks)

Assigning to a key either changes it or creates it. There is no separate "add" — which is convenient, and means a misspelt key quietly creates a new entry:

A typo creates a new key

marks = {"maths": 78}

marks["mathss"] = 90

print(marks)

Updating Multiple Items

When several keys need to be added or changed at once, update() is often shorter than repeated assignments.

update

marks = {"maths": 78}

marks.update({"science": 85, "maths": 90})
marks.update(english=60)
marks.update([("history", 45)])

print(marks)

update() adds what is new and overwrites what already exists.

Removing Dictionary Items

Here is how to take pairs out again.

Using del

The del statement removes a key and its value completely from the dictionary.

del

marks = {"maths": 78, "science": 85}

del marks["maths"]

print(marks)

del on a missing key

marks = {"maths": 78}

del marks["history"]

Using pop()

pop() removes a key and hands the value back, with an optional fallback.

pop

marks = {"maths": 78, "science": 85}

print(marks.pop("maths"))
print(marks.pop("history", 0))
print(marks)

Using popitem()

Unlike pop(), which removes a specific key, popitem() removes whichever pair was added most recently.

popitem() removes and returns the last pair added.

popitem

marks = {"maths": 78, "science": 85, "english": 60}

print(marks.popitem())
print(marks)

popitem on an empty dictionary

{}.popitem()

Using clear()

clear() empties the whole dictionary at once:

clear

marks = {"maths": 78}

marks.clear()

print(marks, len(marks))

Iterating Through Dictionaries

A dictionary can be looped over, and what the loop hands you depends on which of the three views you ask for.

Iterating Over Keys

Looping over a dictionary gives its keys.

Looping gives the keys

marks = {"maths": 78, "science": 85}

for subject in marks:
    print(subject, marks[subject])

for subject in marks.keys():
    print(subject)

Both lines do the same thing. The first is the usual way.

Iterating Over Values

Sometimes the keys are not needed and only the stored values matter.

values

marks = {"maths": 78, "science": 85}

for mark in marks.values():
    print(mark)

print(sum(marks.values()))
print(max(marks.values()))

Iterating Over Key-Value Pairs

When both the key and value are needed together, items() is usually the best choice.

items gives both

marks = {"maths": 78, "science": 85}

for subject, mark in marks.items():
    print(f"{subject}: {mark}")

items() hands over a tuple per pair, which the for line unpacks — exactly the packing and unpacking from the tuples lesson.

Using items(), keys() and values()

These three give views: live windows onto the dictionary, not copies.

A view sees later changes

marks = {"maths": 78}

keys = marks.keys()

print("before:", list(keys))

marks["science"] = 85

print("after: ", list(keys))

The view was taken before science existed and shows it anyway.

That also means you cannot add or remove while looping:

Changing the size during a loop

marks = {"maths": 78, "science": 85}

for subject in marks:
    if marks[subject] < 80:
        del marks[subject]

RuntimeError: dictionary changed size during iteration — a clearer failure than the silent skipping a list gives.

Loop over a copy of the keys instead

marks = {"maths": 78, "science": 85}

for subject in list(marks):
    if marks[subject] < 80:
        del marks[subject]

print(marks)

Or build a new dictionary

marks = {"maths": 78, "science": 85}

kept = {subject: mark for subject, mark in marks.items() if mark >= 80}

print(kept)

That last one is a dictionary comprehension, the same idea as a list comprehension with a key and a value.

Dictionary Operators

Dictionaries have an operator of their own, new enough that you may not have met it.

Access Operator

The square-bracket operator is used both to read existing values and to create or update entries.

Reading and writing with []

marks = {"maths": 78}

print(marks["maths"])

marks["science"] = 85

print(marks)

Dictionary Union

Sometimes two dictionaries need to be combined into one. Python provides an operator for this.

Union

term1 = {"maths": 78, "science": 85}
term2 = {"science": 92, "english": 60}

print(term1 | term2)
print(term1)

The right-hand side wins where both have the same key, so science came out as 92.

Which side wins

print({"maths": 78} | {"maths": 90})
print({"maths": 90} | {"maths": 78})

Dictionary Update Union

|= changes the dictionary on the left in place.

Update union

marks = {"maths": 78}
other_name = marks

marks |= {"science": 85}

print(marks)
print("the other name sees it too:", other_name)

Unlike |, this changes the existing dictionary, so anything else holding it sees the change. It does the same job as update().

Dictionary Methods

Here are all the methods in one run, and then the two that need explaining:

Every method, in order

marks = {"maths": 78, "science": 85}

print("get:       ", marks.get("maths"), marks.get("history", 0))
print("keys:      ", list(marks.keys()))
print("values:    ", list(marks.values()))
print("items:     ", list(marks.items()))
print("copy:      ", marks.copy())
print("setdefault:", marks.setdefault("english", 0), marks)
print("pop:       ", marks.pop("maths"), marks)
print("popitem:   ", marks.popitem(), marks)

marks.update({"history": 45})
print("update:    ", marks)

marks.clear()
print("clear:     ", marks)

print("fromkeys:  ", dict.fromkeys(["maths", "science"], 0))

setdefault()

setdefault() returns the value for a key, and inserts it with a default if it is missing.

setdefault against get

marks = {"maths": 78}

print(marks.get("history", 0))
print("after get:", marks)

print(marks.setdefault("history", 0))
print("after setdefault:", marks)

get() only looks. setdefault() looks and creates. It is at its best for grouping:

Grouping with setdefault

results = [("maths", 78), ("science", 85), ("maths", 90)]

by_subject = {}
for subject, mark in results:
    by_subject.setdefault(subject, []).append(mark)

print(by_subject)

One line builds the list the first time and appends to it every other time.

fromkeys()

fromkeys() makes a new dictionary with keys you supply and one shared value:

fromkeys

subjects = ["maths", "science", "english"]

print(dict.fromkeys(subjects, 0))
print(dict.fromkeys(subjects))

One trap: all the keys share one default value, which matters if that value is a list.

A shared mutable default

shared = dict.fromkeys(["maths", "science"], [])

shared["maths"].append(78)

print(shared)

Both subjects show the mark, because both keys point at the same list. Use a comprehension instead:

A separate list for each key

separate = {subject: [] for subject in ["maths", "science"]}

separate["maths"].append(78)

print(separate)

The full method list, for reference:

MethodWhat it doesGives back
get(k, d)the value, or d if missingthe value
keys()a live view of the keysa view
values()a live view of the valuesa view
items()a live view of the pairsa view
copy()a shallow copya new dictionary
setdefault(k, d)the value, inserting d if missingthe value
update(other)adds and overwritesnothing
pop(k, d)removes kthe value
popitem()removes the last pairthe pair
clear()empties itnothing
fromkeys(ks, v)a new dictionary from keysa new dictionary

copy() is shallow, exactly as with lists. A dictionary holding lists or dictionaries needs copy.deepcopy(), or changes reach through the copy into the original.

Shallow against deep

import copy

school = {"Asha": {"maths": 78}}

shallow = school.copy()
deep = copy.deepcopy(school)

school["Asha"]["maths"] = 90

print("shallow:", shallow)
print("deep:   ", deep)

Built-in Functions with Dictionaries

The familiar built-ins work on dictionaries, with one thing to know about which half they see:

The built-ins that work on a dictionary

marks = {"maths": 78, "science": 85, "english": 60}

print("len:   ", len(marks))
print("str:   ", str(marks))
print("type:  ", type(marks).__name__)
print("dict:  ", dict([("maths", 78)]))
print("sorted:", sorted(marks))
print("max:   ", max(marks))
print("min:   ", min(marks))

sorted(), max() and min() work on the keys, because looping over a dictionary gives keys.

Sorting by value instead of by key

marks = {"maths": 78, "science": 85, "english": 60}

print(sorted(marks))
print(sorted(marks, key=marks.get))
print(max(marks, key=marks.get))
print(sorted(marks.items(), key=lambda pair: pair[1], reverse=True))

key=marks.get says "sort the keys by what each one looks up", which is how you find the best subject.

Totals and averages

marks = {"maths": 78, "science": 85, "english": 60}

print("Total:  ", sum(marks.values()))
print("Average:", round(sum(marks.values()) / len(marks), 1))
print("Best:   ", max(marks, key=marks.get))

Common mistakes

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

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

MistakeWhat you seeWhy it happensFix
Reading a key that is missingKeyError: 'history'[] raises when the key is absentget(key, default)
A typo in a key when assigningA new entry appears silentlyAssignment creates or updatesCheck with in, or print the keys
A repeated key in a literalThe earlier value disappearsThe last one assigned winsKeys must be unique
A list as a keyTypeError: unhashable type: 'list'Keys must be unchangeableUse a tuple
Adding or removing during a loopRuntimeError: dictionary changed size during iterationThe view is liveLoop over list(marks)
Expecting keys() to be a snapshotIt shows later changesIt is a live viewlist(marks.keys())
copy() on a nested dictionaryInner data still sharedThe copy is shallowcopy.deepcopy()
fromkeys with a list defaultEvery key shares one listThe default is created once{k: [] for k in keys}
sorted(marks) expecting valuesThe keys come back sortedLooping gives keyssorted(marks.values())
setdefault() where get() was meantThe key is created as a side effectsetdefault insertsUse get() to only look
Expecting | to change the left sideThe original is untouched| builds a new dictionaryUse |= or update()

What to Remember

  • A dictionary stores key-value pairs, looked up by key rather than by position.
  • Keys are unique, and a repeated key replaces the earlier value with no warning.
  • Keys must be hashable — text, numbers, tuples — and values may be anything.
  • Pairs stay in the order they were added, guaranteed since Python 3.7.
  • marks["history"] raises KeyError; marks.get("history", 0) does not.
  • Assigning to a key creates it if it is missing, so a typo makes a new entry silently.
  • Looping over a dictionary gives keys; use .values() or .items() for the rest.
  • keys(), values() and items() are live views, not snapshots.
  • Changing the size during a loop raises RuntimeError. Loop over list(marks), or build a new dictionary.
  • setdefault() looks and inserts; get() only looks.
  • | builds a new dictionary with the right-hand side winning; |= changes the left one in place.
  • copy() is shallow — nested data needs copy.deepcopy().
  • sorted(), max() and min() work on the keys; add key=marks.get to go by value.

Next: Handling Exception