Перейти к содержимому
Editor

Вложенные списки и словари в Python: сложные структуры данных

Средний25 min6 упр.85 XP

Этот урок переведён с английского оригинала.Read in EnglishLeer en españolСообщить об ошибке в переводе

0/6 упр.

Представьте себе школу. В школе есть классы. В каждом классе есть ученики. У каждого ученика есть имя, возраст и список оценок. Это данные внутри данных внутри данных, то есть вложенные данные.

В реальном программировании вложенные данные встречаются повсюду. Соцсеть хранит пользователей, у которых есть посты, а у каждого поста есть комментарии. Игра хранит уровни с врагами, а у каждого врага есть характеристики. В интернет-магазине есть категории с товарами, а у товаров есть отзывы.

В этом уроке вы научитесь создавать вложенные списки и словари, получать из них данные, изменять и перебирать их. Эти навыки необходимы для работы с реальными данными, особенно с JSON от веб-API.

Как работают списки внутри списков?

Элементами списка могут быть другие списки. Так получается двумерная структура, похожая на сетку или таблицу. Представьте электронную таблицу со строками и столбцами.

Вложенный список в роли сетки
# A 3x3 grid (like a tic-tac-toe board)
board = [
['X', 'O', 'X'],
['O', 'X', 'O'],
['X', ' ', 'O'],
]
for row in board:
print(row)
Вывод
['X', 'O', 'X']
['O', 'X', 'O']
['X', ' ', 'O']

Чтобы получить элемент вложенного списка, используют две пары квадратных скобок. Первая выбирает строку, вторая выбирает столбец.

Доступ к элементам по двум индексам
board = [
['X', 'O', 'X'],
['O', 'X', 'O'],
['X', ' ', 'O'],
]
# board[row][column]
print(board[0][0]) # X (top-left)
print(board[1][1]) # X (center)
print(board[2][1]) # (space) (bottom-middle)
Вывод
X
X

Тем же синтаксисом с двумя парами скобок можно и изменять элементы. Именно так делается ход в игре «крестики-нолики».

Изменение элементов вложенного списка
board = [
[' ', ' ', ' '],
[' ', ' ', ' '],
[' ', ' ', ' '],
]
# Place some moves
board[0][0] = 'X' # top-left
board[1][1] = 'O' # center
board[0][2] = 'X' # top-right
for row in board:
print(row)
Вывод
['X', ' ', 'X']
[' ', 'O', ' ']
[' ', ' ', ' ']

Как работают вложенные словари?

Значением словаря может быть другой словарь. Так получается древовидная структура, в которой вы спускаетесь по ключам всё глубже, пока не доберётесь до нужных данных.

Вложенные словари и цепочка ключей
school = {
'Room 101': {
'teacher': 'Ms. Smith',
'subject': 'Math',
'students': 28,
},
'Room 102': {
'teacher': 'Mr. Jones',
'subject': 'English',
'students': 25,
},
}
print(school['Room 101']['teacher']) # Ms. Smith
print(school['Room 102']['students']) # 25
Вывод
Ms. Smith
25

Каждая пара квадратных скобок опускает вас на уровень глубже. school['Room 101'] даёт внутренний словарь. Затем ['teacher'] даёт значение из этого внутреннего словаря.

Добавлять новые ключи и обновлять значения можно на любом уровне.

Изменение вложенных словарей
school = {
'Room 101': {
'teacher': 'Ms. Smith',
'subject': 'Math',
},
}
# Update an existing value
school['Room 101']['subject'] = 'Algebra'
# Add a new key to the inner dict
school['Room 101']['students'] = 30
# Add an entirely new room
school['Room 103'] = {
'teacher': 'Ms. Lee',
'subject': 'Science',
'students': 22,
}
for room, info in school.items():
print(f"{room}: {info['teacher']} teaches {info['subject']}")
Вывод
Room 101: Ms. Smith teaches Algebra
Room 103: Ms. Lee teaches Science

Списки словарей: самый частый приём

Чаще всего в реальном программировании встречается список словарей. Каждый словарь описывает одну запись (ученика, товар, пользователя), а список хранит все записи вместе.

Если вы когда-нибудь видели данные из веб-API или базы данных, то почти наверняка они выглядели как список словарей.

Список словарей: записи об учениках
students = [
{'name': 'Alice', 'age': 15, 'grade': 92},
{'name': 'Bob', 'age': 16, 'grade': 85},
{'name': 'Charlie', 'age': 15, 'grade': 97},
]
# Access a specific student
print(students[0]['name']) # Alice
print(students[2]['grade']) # 97
# Loop through all students
for student in students:
print(f"{student['name']} is {student['age']} and scored {student['grade']}")
Вывод
Alice
97
Alice is 15 and scored 92
Bob is 16 and scored 85
Charlie is 15 and scored 97

Шаблон всегда один: list[index][key]. Индекс выбирает словарь. Ключ выбирает значение внутри этого словаря.

Как безопасно добраться до глубоко вложенных данных

Реальные данные бывают вложены на несколько уровней. В компании есть отделы, в каждом отделе есть команды, в каждой команде есть сотрудники. Чтобы добраться до глубоко вложенных данных, приходится выстраивать цепочку из нескольких пар скобок.

Цепочка скобок для доступа вглубь
company = {
'engineering': {
'backend': {
'members': ['Alice', 'Bob'],
'lead': 'Alice',
},
'frontend': {
'members': ['Charlie', 'Diana'],
'lead': 'Charlie',
},
},
}
# Get the backend team lead
print(company['engineering']['backend']['lead']) # Alice
# Get the second frontend team member
print(company['engineering']['frontend']['members'][1]) # Diana
Вывод
Alice
Diana

Опасность глубокой вложенности в том, что любой отсутствующий ключ вызывает KeyError. Если данные могут быть неполными, используйте метод .get(): он возвращает None (или значение по умолчанию), а не завершается ошибкой.

Безопасный доступ к вложенным данным через .get()
user = {
'name': 'Alice',
'profile': {
'bio': 'Python developer',
},
}
# Safe access with .get()
print(user.get('profile', {}).get('bio', 'No bio')) # Python developer
print(user.get('profile', {}).get('website', 'No site')) # No site
print(user.get('settings', {}).get('theme', 'default')) # default
Вывод
Python developer
No site
default

Как перебирать вложенные структуры данных

Для вложенных данных часто нужны вложенные циклы. Внешний цикл проходит по элементам верхнего уровня, а внутренний по элементам внутри каждого из них.

Перебор словаря списков
# A class with students and their grades
classes = {
'Math': [85, 92, 78, 95],
'English': [88, 76, 91],
'Science': [93, 87, 82, 79, 95],
}
for subject, grades in classes.items():
average = sum(grades) / len(grades)
print(f"{subject}: average = {average:.1f}")
Вывод
Math: average = 87.5
English: average = 85.0
Science: average = 87.2
Перебор двумерной сетки с enumerate
# Nested list: a grid of numbers
grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
# Print each number with its position
for row_index, row in enumerate(grid):
for col_index, value in enumerate(row):
print(f"grid[{row_index}][{col_index}] = {value}")
Вывод
grid[0][0] = 1
grid[0][1] = 2
grid[0][2] = 3
grid[1][0] = 4
grid[1][1] = 5
grid[1][2] = 6
grid[2][0] = 7
grid[2][1] = 8
grid[2][2] = 9

Для списков словарей обычно хватает одного цикла, потому что каждый словарь представляет собой одну законченную запись.

Фильтрация списка словарей с проверкой вложенного списка
products = [
{'name': 'Laptop', 'price': 999, 'tags': ['electronics', 'computers']},
{'name': 'Book', 'price': 15, 'tags': ['education', 'reading']},
{'name': 'Phone', 'price': 699, 'tags': ['electronics', 'mobile']},
]
# Find all electronics
for product in products:
if 'electronics' in product['tags']:
print(f"{product['name']}: ${product['price']}")
Вывод
Laptop: $999
Phone: $699

Как создавать вложенные структуры данных с нуля

Вложенные данные не всегда приходят к вам готовыми: иногда их нужно собрать самостоятельно. Частый приём: начать с пустой структуры и добавлять в неё элементы внутри цикла.

Строим словарь списков, группируя данные
# Build a dictionary that groups words by their first letter
words = ['apple', 'avocado', 'banana', 'blueberry', 'cherry', 'coconut']
by_letter = {}
for word in words:
first = word[0]
if first not in by_letter:
by_letter[first] = []
by_letter[first].append(word)
for letter, group in by_letter.items():
print(f"{letter}: {group}")
Вывод
a: ['apple', 'avocado']
b: ['banana', 'blueberry']
c: ['cherry', 'coconut']

Ещё одна частая задача: превратить плоские данные во вложенную структуру. Например, сделать из плоского списка баллов список словарей.

Объединяем параллельные списки в список словарей
# Transform parallel lists into a list of dicts
names = ['Alice', 'Bob', 'Charlie']
ages = [15, 16, 15]
grades = [92, 85, 97]
students = []
for i in range(len(names)):
student = {
'name': names[i],
'age': ages[i],
'grade': grades[i],
}
students.append(student)
for s in students:
print(s)
Вывод
{'name': 'Alice', 'age': 15, 'grade': 92}
{'name': 'Bob', 'age': 16, 'grade': 85}
{'name': 'Charlie', 'age': 15, 'grade': 97}

Практические упражнения

Доступ к элементам вложенного списка
Напишите код

У вас есть сетка чисел 3x3. Выведите центральное значение (строка 1, столбец 1), а затем значение в правом нижнем углу (строка 2, столбец 2). Каждое на отдельной строке.

grid = [
[10, 20, 30],
[40, 50, 60],
[70, 80, 90],
]
# Print the center value
# Print the bottom-right value
Предскажите вывод: доступ к вложенному словарю
Угадайте вывод

Внимательно прочитайте код. Что он выведет? Впишите вывод точно в том виде, в каком его напечатает Python.

data = {
'user': {
'name': 'Alice',
'scores': [80, 90, 100],
},
}
print(data['user']['name'])
print(data['user']['scores'][2])
Найдите лучшего ученика
Напишите код

Переберите список учеников и найдите того, у кого самая высокая оценка. Выведите его имя и оценку в формате: Best: Name (Grade)

students = [
{'name': 'Alice', 'grade': 88},
{'name': 'Bob', 'grade': 95},
{'name': 'Charlie', 'grade': 72},
{'name': 'Diana', 'grade': 91},
]
# Find and print the highest scorer
Сгруппируйте слова по первой букве
Напишите код

Сгруппируйте слова в словарь по первой букве. Затем выведите каждую группу на отдельной строке в формате: letter: [words]

Выводите буквы в алфавитном порядке.

words = ['ant', 'bear', 'apple', 'bat', 'cat', 'cow']
# Group by first letter and print
Исправьте ошибку: ловушка вложенного списка
Исправьте ошибку

Этот код пытается создать сетку 3x3 и записать «X» только в центральную клетку. Но что-то не так: запустите его и убедитесь сами! Исправьте ошибку, чтобы «X» стоял только в центре, а во всех остальных клетках осталась «.».

Каждую строку сетки выведите на отдельной строке.

row = ['.'] * 3
grid = [row] * 3
grid[1][1] = 'X'
for r in grid:
print(r)
Посчитайте средние оценки по курсам
Напишите код

У вас есть словарь, в котором каждый ключ означает название курса, а каждое значение представляет собой список словарей с учениками. Посчитайте среднюю оценку по каждому курсу и выведите её в формате: ClassName: average

Округлите каждое среднее до одного знака после точки. Выводите курсы в алфавитном порядке.

classes = {
'Math': [
{'name': 'Alice', 'grade': 90},
{'name': 'Bob', 'grade': 80},
],
'English': [
{'name': 'Charlie', 'grade': 85},
{'name': 'Diana', 'grade': 95},
{'name': 'Eve', 'grade': 78},
],
}
# Calculate and print each class average

Похожие уроки

Урок был полезен?