# Dictionary

Course: [Python](https://www.devtutorials.ai/learn/python) · Section: Collections
Source: https://www.devtutorials.ai/learn/python/collections/dictionary

> 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:

```python runnable title="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.

```python runnable title="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:

<div className="tscroll">

| 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 |

</div>

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

```python runnable title="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:

```python runnable title="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:

```python runnable title="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.

```python runnable title="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.

```python runnable title="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:

```python runnable title="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.

```python runnable title="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)])
```

```python runnable expect-error title="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.

```python runnable title="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:

<div className="tscroll">

| 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 |

</div>

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

```python runnable expect-error title="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:

```python runnable title="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.

```python runnable title="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.

```python runnable title="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:

```python runnable title="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.

```python runnable title="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.

```python runnable title="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.

```python runnable expect-error title="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.

```python runnable title="get"
marks = {"maths": 78}

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

```python runnable title="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.

```python runnable title="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.

```python runnable title="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.

```python runnable title="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:

```python runnable title="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.

```python runnable title="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.

```python runnable title="del"
marks = {"maths": 78, "science": 85}

del marks["maths"]

print(marks)
```

```python runnable expect-error title="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.

```python runnable title="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.

```python runnable title="popitem"
marks = {"maths": 78, "science": 85, "english": 60}

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

```python runnable expect-error title="popitem on an empty dictionary"
{}.popitem()
```

### Using clear()

`clear()` empties the whole dictionary at once:

```python runnable title="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**.

```python runnable title="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.

```python runnable title="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.

```python runnable title="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.

```python runnable title="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:

```python runnable expect-error title="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.

```python runnable title="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)
```

```python runnable title="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.

```python runnable title="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.

```python runnable title="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.

```python runnable title="Which side wins"
print({"maths": 78} | {"maths": 90})
print({"maths": 90} | {"maths": 78})
```

### Dictionary Update Union

`|=` changes the dictionary on the left in place.

```python runnable title="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:

```python runnable title="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**.

```python runnable title="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:

```python runnable title="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:

```python runnable title="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.

```python runnable title="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:

```python runnable title="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:

<div className="tscroll">

| 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 |

</div>

`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.

```python runnable title="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:

```python runnable title="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.

```python runnable title="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.

```python runnable title="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.

<div className="tscroll">

| 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()` |

</div>

## 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**
