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:
| Characteristic | What it means |
|---|---|
| Key-value pairs | each key leads to one value |
| Unique keys | a repeated key replaces the earlier one |
| Changeable | you can add, change and remove pairs |
| Ordered | pairs stay in the order they were added |
| Fast lookup | finding a key does not search pair by pair |
| Hashable keys only | values 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:
| Key | Allowed? | Why |
|---|---|---|
"maths" | yes | text cannot change |
78 | yes | a number cannot change |
(1, 2) | yes | a tuple of unchangeable items |
None | yes | there is only one None |
[1, 2] | no | a list can change |
{1, 2} | no | a set can change |
(1, [2]) | no | the 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:
| Method | What it does | Gives back |
|---|---|---|
get(k, d) | the value, or d if missing | the value |
keys() | a live view of the keys | a view |
values() | a live view of the values | a view |
items() | a live view of the pairs | a view |
copy() | a shallow copy | a new dictionary |
setdefault(k, d) | the value, inserting d if missing | the value |
update(other) | adds and overwrites | nothing |
pop(k, d) | removes k | the value |
popitem() | removes the last pair | the pair |
clear() | empties it | nothing |
fromkeys(ks, v) | a new dictionary from keys | a 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.
| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
| Reading a key that is missing | KeyError: 'history' | [] raises when the key is absent | get(key, default) |
| A typo in a key when assigning | A new entry appears silently | Assignment creates or updates | Check with in, or print the keys |
| A repeated key in a literal | The earlier value disappears | The last one assigned wins | Keys must be unique |
| A list as a key | TypeError: unhashable type: 'list' | Keys must be unchangeable | Use a tuple |
| Adding or removing during a loop | RuntimeError: dictionary changed size during iteration | The view is live | Loop over list(marks) |
Expecting keys() to be a snapshot | It shows later changes | It is a live view | list(marks.keys()) |
copy() on a nested dictionary | Inner data still shared | The copy is shallow | copy.deepcopy() |
fromkeys with a list default | Every key shares one list | The default is created once | {k: [] for k in keys} |
sorted(marks) expecting values | The keys come back sorted | Looping gives keys | sorted(marks.values()) |
setdefault() where get() was meant | The key is created as a side effect | setdefault inserts | Use get() to only look |
Expecting | to change the left side | The original is untouched | | builds a new dictionary | Use |= 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"]raisesKeyError;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()anditems()are live views, not snapshots.- Changing the size during a loop raises
RuntimeError. Loop overlist(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 needscopy.deepcopy().sorted(),max()andmin()work on the keys; addkey=marks.getto go by value.
Next: Handling Exception