Skip to content

Глава 4. Списки: коллекция баллов группы, срезы и рейтинг

Задача главы: научиться хранить баллы группы одним объектом — списком, — получать доступ к элементам по индексам и срезам, безопасно копировать список и строить из него отсортированный рейтинг.


4.1 Список баллов группы: создание, len(), .append(), .insert(), .remove(), del

Зачем это нужно. У группы из тридцати человек невозможно (и незачем) заводить тридцать отдельных переменных score_1, score_2, ... — вся аналитика встанет колом при первой же попытке посчитать средний балл. Список хранит любое количество баллов одним объектом, с которым можно работать целиком.

// PYTHON_EXECUTOR (Pyodide Wasm)
ОперацияЧто делаетИщет
.append(x)добавляет x в конец списка
.insert(i, x)вставляет x перед позицией iпо позиции
.remove(x)удаляет первое вхождение значения xпо значению
del list[i]удаляет элемент на позиции iпо позиции

⚠️ Подводный камень

Список — изменяемый (mutable) объект. В отличие от строк из главы Строки и f-строки, методы .append(), .insert(), .remove() правят список на месте и возвращают None. Присваивание результата обратно в переменную не сохраняет изменения, а стирает весь список.

// PYTHON_EXECUTOR (Pyodide Wasm)
Traceback (most recent call last):
File "report.py", line 6, in <module>
group_scores.append(80)
AttributeError: 'NoneType' object has no attribute 'append'
ОшибкаЧто значит для PythonЧеловеческий перевод
— (тихая ошибка)«.append() изменил список на месте и вернул None»Присвоили результат .append() обратно в переменную — список подменился на None. .append(), .insert(), .remove().sort() из раздела 4.4) работают на месте и ничего не возвращают: вызывайте их отдельной строкой, без =.
AttributeError«У None нет метода append»Прямое следствие предыдущей ошибки: как только список стал None, любое обращение к нему как к списку падает.

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)

Пояснение логики: список меняется методом сам по себе, поэтому переменной уже присвоен нужный объект — второе присваивание не только лишнее, но и разрушительное. Правило простое: если метод не печатает и не возвращает то, что логично было бы вывести, скорее всего он работает на месте.


4.2 Индексы и доступ к элементам: отрицательные индексы

Зачем это нужно. Нужно быстро достать конкретный балл — например, результат первого сдавшего или последней добавленной работы, — без просмотра всего списка.

// PYTHON_EXECUTOR (Pyodide Wasm)

Здесь и кроется главная путаница: Python нумерует элементы с нуля, а человек — с единицы. «Третья строка ведомости» — это group_scores[2], а не group_scores[3]. Перевод простой: индекс = номер_по_ведомости - 1.

⚠️ Подводный камень

len() возвращает количество элементов, а не индекс последнего. У списка из 5 элементов индексы — 0, 1, 2, 3, 4, и group_scores[5] (то есть group_scores[len(group_scores)]) уже не существует.

// PYTHON_EXECUTOR (Pyodide Wasm)
Traceback (most recent call last):
File "report.py", line 3, in <module>
last_score = group_scores[len(group_scores)]
IndexError: list index out of range
ОшибкаЧто значит для PythonЧеловеческий перевод
IndexError«Индекса 5 в списке из 5 элементов нет»len(group_scores) равен 5, но последний индекс — 4. Смешали «сколько элементов» и «какой у последнего индекс».

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)

Пояснение логики: отрицательные индексы существуют именно для «последнего», «предпоследнего» и так далее — считать длину вручную не нужно. А там, где номер приходит «по-человечески» (из ведомости, из формы), в индекс он превращается одним и тем же движением: - 1.


4.3 Срезы списка и копирование [:]

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

// PYTHON_EXECUTOR (Pyodide Wasm)

Правило то же, что и у срезов строк из главы Строки и f-строки: правая граница не включается, [start:stop] — от start включительно до stop не включая.

⚠️ Подводный камень

= не копирует список, а даёт ему второе имя. Список — изменяемый объект: обе переменные указывают на один и тот же участок памяти, и правка через любое из имён видна в обоих.

// PYTHON_EXECUTOR (Pyodide Wasm)
ОшибкаЧто значит для PythonЧеловеческий перевод
— (тихая ошибка)«backup_scores — второе имя того же списка в памяти»backup_scores = group_scores не создаёт новый объект, а даёт старому второй ярлык. Изменение через любое из имён меняет один и тот же список. Нужна отдельная копия — полный срез [:] или list().

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)

Пояснение логики: срез всегда создаёт новый список, даже если границы не заданы — [:] означает «от начала до конца», то есть все элементы, но уже сложенные в новый объект. Присваивание (=) — это не «сохранить копию», а «дать ещё одно имя»; копию делает только срез или list().


4.4 Сортировка и рейтинг: .sort() против sorted(), reverse=True, key=

Зачем это нужно. Рейтинг группы — это отсортированный список: от лучшего к худшему для похвальной грамоты, от худшего к лучшему для списка на пересдачу. У Python для этого есть два похожих по названию, но разных по поведению инструмента.

// PYTHON_EXECUTOR (Pyodide Wasm)

Параметр key= задаёт, по чему сравнивать элементы, — не только по значению «как есть»:

// PYTHON_EXECUTOR (Pyodide Wasm)

⚠️ Здесь key=len — готовая функция Python. Собственные функции для key= (например, сортировка по среднему баллу ученика) появятся в главе Функции.

⚠️ Подводный камень

Та же ловушка, что и с .append() в разделе 4.1: .sort() возвращает None, и присваивание результата обратно уничтожает список.

// PYTHON_EXECUTOR (Pyodide Wasm)
ОшибкаЧто значит для PythonЧеловеческий перевод
— (тихая ошибка)«.sort() меняет список на месте и возвращает None»Присвоили результат .sort() обратно в переменную — список стал None. Если нужен результат в отдельной переменной, а исходный порядок важен — используйте sorted(), а не .sort().

Вторая ловушка — сортировка списка со смешанными типами. В баллы иногда попадает текстовая заглушка вместо числа («не явился»), и сравнение str с int Python не умеет.

// PYTHON_EXECUTOR (Pyodide Wasm)
Traceback (most recent call last):
File "report.py", line 3, in <module>
mixed_scores.sort()
TypeError: '<' not supported between instances of 'str' and 'int'
ОшибкаЧто значит для PythonЧеловеческий перевод
TypeError«Оператор < не работает между str и int»В списке баллов затесалось текстовое значение-заглушка. Сортировка требует однородных по сравнению элементов — сначала замените пропуски числом-маркером (например, 0) или уберите их из списка.

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)

Пояснение логики: sorted() — функция общего назначения, всегда возвращает новый список, поэтому её безопасно присваивать переменной. .sort() — метод самого списка: он экономит память, не создавая копию, но подходит только тогда, когда исходный порядок точно больше не понадобится.


4.5 Мини-словарик ошибок четвёртой главы

ОшибкаЧто значит для PythonЧеловеческий перевод
IndexError«Индекс за пределами длины списка»len(list) — это количество элементов, а не индекс последнего; берите list[-1].
AttributeError«У None нет метода append/sort»Присвоили результат метода, который возвращает None (.append(), .insert(), .remove(), .sort()), обратно в переменную.
TypeError«< не поддерживается между str и int»В списке баллов оказалась текстовая заглушка вместо числа — сортировка требует однородных типов.
— (тихая ошибка)«.append()/.sort() работают на месте и возвращают None»Присваивание x = x.append(...) или x = x.sort() подменяет список на None.
— (тихая ошибка)«= создаёт второе имя того же списка»backup = original — не копия; правки видны в обоих именах. Для копии нужен original[:] или list(original).

Самая коварная из тихих ошибок — общее владение списком через второе имя. Она срабатывает даже там, где явного .append() рядом нет:

// PYTHON_EXECUTOR (Pyodide Wasm)

Traceback здесь не появится — программа отработает и выдаст неверный результат молча. Единственная защита — привычка: если список передаётся дальше «для эксперимента» или «во временную переменную», копируйте его срезом [:] заранее, а не по факту обнаруженной пропажи данных.


Мини-кейс: рейтинг группы — топ-3 и отстающие по среднему баллу

Задача: по спискам ФИО и баллов группы посчитать средний балл, построить рейтинг топ-3 и выделить тех, кто не дотянул до среднего.

// PYTHON_EXECUTOR (Pyodide Wasm)
Средний балл группы: 71.3
--- Топ-3 ---
1 место: Смирнова Ольга — 92
2 место: Петров Пётр — 88
3 место: Иванов Иван — 78
--- Требуют внимания (балл ниже среднего) ---
Ким Юрий: 65 (ниже среднего на 6.3)
Сидорова Анна: 45 (ниже среднего на 26.3)
Фёдоров Глеб: 60 (ниже среднего на 11.3)

Что получилось: пары (score, name) в scored_students не дали баллам и фамилиям разойтись при сортировке — sorted() сравнивает кортежи по первому элементу, поэтому балл остаётся ведущим ключом без отдельного key=. Топ-3 получен срезом готового рейтинга, а не написан вручную. Список отстающих собран отдельно от рейтинга: попадание в него зависит не от места, а от сравнения с average_score, поэтому в него мог бы попасть и человек с четвёртого места, и не попасть никто, если группа сильная.

⚠️ Если бы кто-то из группы не сдавал работу и его балл записали строкой 'н/я', sum(group_scores) упал бы с TypeError ещё на шаге расчётов — устойчивый разбор такой выгрузки ждёт в главе Ошибки и исключения.


Чек-лист главы

  • Храню баллы группы в одном списке, а не в отдельных переменных
  • Помню, что .append(), .insert(), .remove(), .sort() меняют список на месте и возвращают None — не присваиваю результат обратно
  • Достаю элемент по индексу и помню, что нумерация в Python начинается с нуля
  • Использую отрицательные индексы ([-1]) для последнего элемента вместо ручного расчёта длины
  • Перевожу «человеческую» позицию в ведомости в индекс Python через - 1
  • Помню, что len(list) — это количество элементов, а не индекс последнего, и не хожу по list[len(list)]
  • Достаю часть списка срезами [start:stop:step], помня, что правая граница не включается
  • Делаю независимую копию списка через [:] или list(), а не через =
  • Знаю, что backup = original — это второе имя того же списка, а не копия
  • Использую sorted(), когда нужен новый список и важен исходный порядок, и .sort(), когда исходный порядок больше не нужен
  • Задаю направление сортировки через reverse=True, а не переворачиваю список вручную
  • Понимаю, что делает key= в sorted(), на простом примере (key=len)
  • Узнаю TypeError при сортировке списка со смешанными типами и знаю, что данные нужно сначала привести к одному типу

Дальше: Словари