Basics

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

Updated: · Skillaria.Top

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

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

Learn this topic on video

This topic is covered in depth with practice in the course “Python 3.15: от новичка до профи”.

Go to course