# Dictionaries in Python (dict): Creating, Methods, Looping, Sorting and frozendict

URL: https://skillaria.top/en/guides/python/python-dict-dictionaries

A dictionary in Python (dict) is a mutable collection of key–value pairs that finds a value by its key almost instantly; it is created with curly braces, for example {"coffee": 250}, or with the dict() function. Keys are unique and must be hashable; since Python 3.7 insertion order is preserved, and Python 3.15 added a built-in immutable variant — frozendict.

A dictionary in Python (`dict`) is a collection of key–value pairs in which a value is found by its key almost instantly, no matter how many records there are. If you need to total expenses by category, store settings or quickly look up a user by id, you use a dictionary. Below is everything you need to work with a Python dict: creating it, methods, looping, sorting, merging and the novelty of 3.15 — `frozendict`.

## What a dictionary is in Python

A dictionary stores pairs where the key is unique: if you write a value under an existing key, the old one is replaced. Internally it is a hash table, so searching, adding and deleting by key work in constant time on average, not by scanning.

A key must be a hashable object: a string, a number, a tuple of immutable values. A list cannot be a key — you will get `TypeError: unhashable type: 'list'`. Values, however, can be anything, including other dictionaries and lists.

Since Python 3.7 a dictionary is guaranteed to remember insertion order: when you iterate over it, you get the keys in the order they were added.

## How to create a dictionary

There are several ways to create a dictionary, and all of them appear in real code.

```
spent = {"Food": 1200, "Taxi": 450}       # a literal
empty = {}                                 # an empty dictionary
also_empty = dict()                        # the same, via the dict function
user = dict(name="Anna", age=30)           # keyword arguments
pairs = dict([("a", 1), ("b", 2)])         # from a list of pairs
keys = dict.fromkeys(["x", "y"], 0)        # {'x': 0, 'y': 0}
```

Note that empty curly braces `{}` create an empty dictionary, not a set. An empty set is made only with `set()`.

A convenient way to build a dictionary from two lists is `zip`:

```
names = ["Food", "Taxi", "Cinema"]
sums = [1200, 450, 600]
spent = dict(zip(names, sums))
print(spent)   # {'Food': 1200, 'Taxi': 450, 'Cinema': 600}
```

## Access by key: d[k] and get

A value is read with square brackets. If the key is missing, Python raises `KeyError`. To avoid the error, use the `get` method: it returns `None` or a default value.

```
spent = {"Food": 1200}
print(spent["Food"])            # 1200
print(spent.get("Cinema"))      # None
print(spent.get("Cinema", 0))   # 0
spent["Cinema"]                 # KeyError: 'Cinema'
```

To check whether a key exists, use the `in` operator: `"Food" in spent` returns `True`. It checks keys, not values. For values write `1200 in spent.values()`.

## Adding, changing and deleting

Adding or updating an entry is a single assignment line. For counting, the combination with `get` is very convenient:

```
spent = {}
for category, amount in [("Food", 250), ("Taxi", 300), ("Food", 400)]:
    spent[category] = spent.get(category, 0) + amount
print(spent)   # {'Food': 650, 'Taxi': 300}
```

You can delete a key with `del d[k]` (it raises `KeyError` if the key is missing) or with `d.pop(k, default)`, which also returns the value. To empty a dictionary completely, use `d.clear()`. The length of a dictionary, that is the number of pairs, is `len(d)`.

## Dictionary methods: a cheat sheet

| Method | What it does |
| --- | --- |

| `get(k, default)` | the value for the key, or default, without an error |

| `setdefault(k, default)` | returns the value; if the key is missing, first writes default |

| `update(other)` | adds and overwrites pairs from another dictionary |

| `pop(k, default)` | removes the key and returns the value |

| `popitem()` | removes and returns the most recently added pair |

| `keys()`, `values()`, `items()` | views of keys, values and pairs |

| `copy()` | shallow copy |

| `clear()` | empty the dictionary |

| `fromkeys(keys, value)` | a new dictionary with one shared value |

### setdefault: filling a dictionary with lists

The `setdefault` method helps when the values are lists and you need to group items:

```
groups = {}
for item, cat in [("coffee", "Food"), ("metro", "Transport"), ("pizza", "Food")]:
    groups.setdefault(cat, []).append(item)
print(groups)   # {'Food': ['coffee', 'pizza'], 'Transport': ['metro']}
```

### update: updating a dictionary

`update` changes a dictionary in place: new keys are added, matching keys are overwritten.

```
cfg = {"debug": False, "lang": "en"}
cfg.update({"debug": True, "theme": "dark"})
print(cfg)   # {'debug': True, 'lang': 'en', 'theme': 'dark'}
```

## defaultdict: a dictionary with default values

`collections.defaultdict` is a subclass of dict that, when a missing key is accessed, creates the value itself using a factory. Counting and grouping code becomes shorter.

```
from collections import defaultdict

spent = defaultdict(int)
for cat, amount in [("Food", 250), ("Taxi", 300), ("Food", 400)]:
    spent[cat] += amount
print(dict(spent))   # {'Food': 650, 'Taxi': 300}

groups = defaultdict(list)
groups["Food"].append("coffee")
```

For simple counting of occurrences there is also `collections.Counter`: `Counter(["a", "b", "a"])` gives `Counter({'a': 2, 'b': 1})`.

## Iterating over a dictionary: the for loop

A `for` loop over a dictionary goes over the keys by default. To get the key and the value together, iterate over `items()` — this is the most common variant.

```
spent = {"Food": 650, "Taxi": 300}

for cat in spent:                    # keys
    print(cat)

for amount in spent.values():        # values
    print(amount)

for cat, amount in spent.items():    # pairs
    print(f"{cat}: {amount}")
```

You cannot change the size of a dictionary during iteration — you will get `RuntimeError: dictionary changed size during iteration`. If you need to delete some keys, iterate over a copy: `for k in list(d):`.

## Printing a dictionary

`print(d)` prints the dictionary on one line. For a large or nested dictionary, the `pprint` module or `json.dumps(d, ensure_ascii=False, indent=2)` is more convenient — it prints the data with indentation, and non-Latin characters without escaping.

## Sorting a dictionary

A dictionary itself is not sorted in place: it has no `sort` method. Sorting a dictionary means creating a new dictionary from the sorted pairs using `sorted`.

```
spent = {"Taxi": 300, "Food": 650, "Cinema": 120}

by_key = dict(sorted(spent.items()))
print(by_key)    # {'Cinema': 120, 'Food': 650, 'Taxi': 300}

by_value = dict(sorted(spent.items(), key=lambda p: p[1], reverse=True))
print(by_value)  # {'Food': 650, 'Taxi': 300, 'Cinema': 120}
```

If you only need the top N categories, do not convert the result back into a dictionary: `sorted(spent.items(), key=lambda p: p[1], reverse=True)[:2]` returns a list of pairs.

To sort a list of dictionaries by a field, use: `sorted(users, key=lambda u: u["age"])`.

## Merging and combining dictionaries

Since Python 3.9, dictionaries are merged with the `|` operator, and `|=` updates a dictionary in place. When keys clash, the right operand wins.

```
a = {"x": 1, "y": 2}
b = {"y": 20, "z": 3}
print(a | b)          # {'x': 1, 'y': 20, 'z': 3}
print({**a, **b})     # the same via unpacking
a |= b                # the same as a.update(b)
```

Note that `a + b` does not work for dictionaries: it raises `TypeError`. If you really want to add the values for identical keys, use `Counter`: `Counter(a) + Counter(b)`.

### Unpacking a dictionary

Two asterisks `**` unpack a dictionary into named arguments of a function or into another dictionary:

```
def greet(name, city):
    return f"{name} from {city}"

data = {"name": "Anna", "city": "Kazan"}
print(greet(**data))   # Anna from Kazan
```

## Dictionary comprehension

A dictionary comprehension builds a dictionary in one line from any sequence:

```
squares = {n: n * n for n in range(5)}
print(squares)   # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

big = {k: v for k, v in spent.items() if v > 200}

inverted = {v: k for k, v in {"a": 1, "b": 2}.items()}
print(inverted)  # {1: 'a', 2: 'b'}
```

The last example shows how to invert a dictionary by swapping keys and values. It works if the values are unique and hashable; otherwise some pairs will be lost.

## Nested dictionaries

A value can be another dictionary — this is how you describe structures such as JSON responses from APIs, or a dictionary of user dictionaries.

```
users = {
    101: {"name": "Anna", "skills": ["python", "sql"]},
    102: {"name": "Oleg", "skills": ["go"]},
}
print(users[101]["skills"][0])            # python
print(users.get(103, {}).get("name"))     # None, without KeyError
```

## Copying a dictionary: a copy and a second name

The statement `b = a` does not copy a dictionary: it attaches a second name to the same object. Change it through `b` and you will see the change in `a`. The same happens when a dictionary is passed to a function: the function gets a reference to the shared object and can quietly spoil it.

```
import copy

a = {"limits": [0, 500]}
b = a.copy()              # shallow copy
b["limits"].append(1000)  # the nested list is shared!
print(a)                  # {'limits': [0, 500, 1000]}

c = copy.deepcopy(a)      # a fully independent copy
```

## Comparing dictionaries

The `==` operator compares dictionaries by content: they are equal if all pairs match. The order of keys is not taken into account: `{"a": 1, "b": 2} == {"b": 2, "a": 1}` gives `True`. To find common keys, use set operations on the views: `a.keys() & b.keys()`.

## Typed dictionaries: annotations

A dictionary annotation is written as `dict[str, int]`. If a dictionary has a fixed set of fields of different types, the `TypedDict` class from the `typing` module is convenient:

```
from typing import TypedDict

class User(TypedDict):
    name: str
    age: int

u: User = {"name": "Anna", "age": 30}
```

Annotations are not checked at run time; they are read by IDEs and analysers such as mypy.

## An immutable dictionary: frozendict in Python 3.15

Python 3.15 introduced the built-in type `frozendict` (PEP 814): no import is needed, and an attempt to change it immediately ends with a `TypeError`. Previously an immutable dictionary had to be imitated with a wrapper from the `types` module or with a third-party package. This is exactly what is needed for shared settings that no function should quietly change.

```
config = frozendict(min_amount=0, max_amount=500)
config["max_amount"] = 1000
# TypeError: 'frozendict' object does not support item assignment

strict = config | {"max_amount": 300}   # a new frozendict, the original is intact
cache = {config: "report for May"}      # hashable — works as a key
```

What to know, according to the course materials:

- `frozendict` inherits from `object`, not from `dict`, so `isinstance(cfg, dict)` returns `False`. To check for "any dictionary", use `collections.abc.Mapping` or `isinstance(x, (dict, frozendict))`.
- It is hashable only if its keys and values are hashable: `frozendict(a=[1])` can be created, but `hash()` will fail. The immutability is shallow, like that of a tuple.
- The order is kept, but `==` does not take it into account.
- `json.dumps`, `pickle` and `copy` understand frozendict.

## Common mistakes

- **KeyError** when reading — use `get` or an `in` check.
- **A list as a key** — convert it to a tuple: `d[tuple(lst)]`.
- **Changing a dictionary inside a loop** — iterate over `list(d)`.
- **Expecting a copy from an assignment** — use `copy()` or `deepcopy`.
- **`{}` instead of an empty set** — it is an empty dictionary.

## Summary

The Python dict is the language's main data structure: fast lookup by key, preserved insertion order, convenient methods `get`, `setdefault`, `update`, iteration through `items()`, sorting through `sorted` and merging with the `|` operator. Remember about copying and hashable keys, and for settings in Python 3.15 use `frozendict`. Dictionaries, lists, tuples and sets are practised hands-on in our course "Python 3.15: from beginner to pro", using an expense tracker as the example.

## FAQ

**Can a dictionary hold values of different types?**

Yes, values can be anything and different within one dictionary: numbers, strings, lists, other dictionaries. The only restriction applies to keys — they must be hashable.

**How do I find out how many items a dictionary has?**

The function len(d) returns the number of key–value pairs. For an empty dictionary it returns 0, and an empty dictionary counts as false in an if condition.

**How does dict differ from defaultdict?**

defaultdict is a subclass of dict that, instead of raising KeyError on a missing key, creates a value through a factory such as int or list. In every other respect it behaves like an ordinary dictionary.

**How do I get a key by its value?**

There is no direct method; you need a search: next((k for k, v in d.items() if v == value), None). If you search this way often, it is simpler to build a reverse dictionary with a comprehension {v: k for k, v in d.items()}.

**How do I save a dictionary to a file?**

Most often through the json module: json.dump(d, f, ensure_ascii=False, indent=2) into a file opened for writing, and json.load(f) to read it back. The keys become strings in the process.


---
Source: Skillaria.Top (https://skillaria.top)
