Модуль 4 · Урок 4.3
Кортежи, распаковка, enumerate и zip
Небольшие неизменяемые записи, раздача элементов по именам и совместный перебор.
Результат ученика — это пара «имя и балл», точка на плоскости — пара координат. Такие короткие записи не должны случайно меняться. Для них есть кортеж — неизменяемая последовательность, а распаковка раздаёт его элементы по именам одним присваиванием. Распаковка работает и для списков из прошлого урока.
Данные урока — результаты учеников парами «имя и балл»: ("Анна", 91), ("Илья", 84), ("Марк", 95).
Кортеж
Кортеж похож на список, но его нельзя изменить. У кортежа (tuple) нельзя заменить элемент, добавить или удалить позицию. Он подходит для небольшой записи: координат, пары «имя и результат», нескольких возвращаемых значений.
point = (3, 5)print(point[0])single = (8,)empty = ()print(type((8)), type(single), len(empty))Кортеж создаёт запятая, а не только круглые скобки. (8) — просто число в скобках, (8,) — кортеж из одного элемента. Попытка заменить элемент остановит программу:
point = (3, 5)point[0] = 4Тонкости:список внутри кортежа
Неизменяемость кортежа не делает неизменяемым список внутри него: в ([1, 2], 3) заменить список нельзя, а добавить в него элемент можно.
Распаковка
Распаковка связывает несколько имён с элементами последовательности.
point = (3, 5)x, y = pointprint(x, y) a = 10b = 20a, b = b, aprint(a, b)Справа сначала вычисляются прежние значения b и a, затем выполняется присваивание. Обычные последовательные строки a = b и b = a обмена не дадут: прежнее a будет потеряно — после них оба имени будут равны 20.
a, b = b, a: правая часть уже вычислена как пара (20, 10), затем имена связаны с её элементами. Прежние связи — пунктиром; сами числа никуда не копировались.Количество значений должно совпадать с количеством имён:
x, y = (1, 2, 3)Можно обозначить имя для остальных элементов звёздочкой:
first, *middle, last = [10, 20, 30, 40]print(first, middle, last)middle получает список. Распаковка работает и для списков, строк и других перебираемых объектов, а не только кортежей.
Пары в for
Пары из списка удобно распаковывать прямо в заголовке for. Выведем имена тех, у кого результат не меньше 90:
students = [("Анна", 91), ("Илья", 84), ("Марк", 95)]for name, score in students: if score >= 90: print(name)Пара из списка сразу распаковывается в name и score. Без распаковки пришлось бы писать for student in students: и обращаться к student[0] и student[1].
enumerate и zip
enumerate добавляет номер к каждому элементу, zip соединяет источники по позициям. enumerate выдаёт пары «номер и элемент»:
names = ["Анна", "Илья", "Марк"]for position, name in enumerate(names, start=1): print(position, name)start=1 меняет выдаваемые номера, но не индексы исходного списка. В каждой итерации пара распаковывается в два имени.
zip соединяет несколько источников по позициям:
names = ["Анна", "Илья", "Марк"]scores = [91, 84]for name, score in zip(names, scores): print(name, score)Балла Марка в списке нет, и zip закончился на коротком списке: третьей пары не будет, а программа не сообщит об этом.
enumerate ставит перед каждым именем номер. Справа zip собирает пары по позициям: первое имя с первым баллом, второе со вторым; Марку пары не досталось.Тонкости:zip без потерь
Если длины по смыслу обязаны совпадать, пиши zip(names, scores, strict=True): при разной длине программа остановится с ValueError, и потеря данных не пройдёт незаметно.
Звёздочка в вызове функции означает ещё одно применение распаковки:
numbers = [10, 20, 30]print(*numbers)print(*numbers, sep=", ")Элементы передаются отдельными аргументами, как в print(10, 20, 30). Это не умножение: значение знака зависит от его места в синтаксисе.
Итог
Кортежи держат короткие записи, а распаковка, enumerate и zip делают перебор пар коротким и понятным. Вот что понадобится дальше.
- Кортеж
(3, 5)— неизменяемая запись; из одного элемента —(8,): кортеж создаёт запятая. - Распаковка
x, y = point: имён столько же, сколько элементов; остаток —*rest. a, b = b, aОбмен: правая часть вычисляется целиком до присваивания.for name, score in pairsПары распаковываются прямо в заголовке цикла.enumerateПары «номер, элемент»;start=1меняет номера, но не индексы.zipПары по позициям; останавливается на коротком источнике.print(*numbers)Элементы списка передаются в print по отдельности.
Проверьте себя
Ответь сначала сам, потом открой ответ.
Чем (8) отличается от (8,)?
ОтветСкрыть ответ
(8) — просто число 8 в скобках, (8,) — кортеж из одного элемента: кортеж создаёт запятая.
Почему строки a = b и b = a не меняют значения местами, а a, b = b, a меняет?
ОтветСкрыть ответ
После a = b прежнее значение a потеряно. В a, b = b, a правая часть сначала целиком вычисляется в пару старых значений, и только потом имена связываются с её элементами.
Что напечатает цикл for a, b in zip([1, 2, 3], "xy"): print(a, b)?
ОтветСкрыть ответ
Две строки: «1 x» и «2 y». zip закончился на короткой строке, тройке пары не досталось.
Что получат first и rest после first, *rest = "PYTHON"?
ОтветСкрыть ответ
first — 'P', rest — ['Y', 'T', 'H', 'O', 'N'].
Что выведет цикл по enumerate(["a", "b", "c"], start=1)? Каков индекс "c" в самом списке?
ОтветСкрыть ответ
1 a, 2 b, 3 c; в списке у "c" индекс 2.
Практика
Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.
Напиши программу: ученики с баллом от 90
Есть список пар [("Оля", 77), ("Петя", 95), ("Вера", 90)]. Напиши программу, которая печатает имя и балл тех, у кого результат не меньше 90, распаковывая пары в заголовке for. Запусти её и запиши, что она напечатала.
Запишите напечатанные строки через пробел в порядке вывода.
Подсказка
for name, score in students: — пара сразу раскладывается на имя и балл.
Разбор
Например:
students = [("Оля", 77), ("Петя", 95), ("Вера", 90)]for name, score in students: if score >= 90: print(name, score)Ответ: Петя 95 Вера 90
Что напечатает программа?
print(type((8)).__name__)print(type((8,)).__name__)print(len((8,)))Запишите короткие имена типов и число через пробел в порядке вывода, без <class …>.
Подсказка
Кортеж создаёт запятая, а не скобки.
Разбор
(8) — число 8 в скобках: int. (8,) — кортеж из одного элемента: tuple, его длина 1.
Ответ: int tuple 1
Что напечатает программа?
a, b = 1, 2a, b = b, a + ba, b = b, a + ba, b = b, a + bprint(a, b)Запишите напечатанные числа через пробел.
Подсказка
Правая часть вычисляется из старых значений целиком, потом имена получают новые.
Разбор
Пары (1, 2) → (2, 3) → (3, 5) → (5, 8).
Ответ: 5 8
Что напечатает программа?
count = 0for pair in zip([1, 2, 3], "ab"): count += 1print(count)Введите число.
Подсказка
zip останавливается на коротком источнике.
Разбор
В строке «ab» два символа, поэтому пар две: (1, 'a') и (2, 'b').
Ответ: 2
Что напечатает программа?
print(*[3, 1, 4], sep=", ")Запишите напечатанную строку целиком.
Подсказка
Звёздочка передаёт элементы в print по отдельности, sep ставится между ними.
Разбор
Это то же, что print(3, 1, 4, sep=", ").
Ответ: 3, 1, 4
Напиши программу: лучший ученик через zip
Имена ["Анна", "Илья", "Марк"] и баллы [91, 84, 95] лежат в двух списках. Напиши программу, которая проходит их через zip и печатает имя и балл лучшего ученика. Запусти её и запиши ответ.
Запишите напечатанную строку: имя и балл через пробел.
Подсказка
Лучшего ищут как в сквозной задаче: начни с первой пары и сравнивай баллы.
Разбор
Например:
names = ["Анна", "Илья", "Марк"]scores = [91, 84, 95]best_name = names[0]best_score = scores[0]for name, score in zip(names, scores): if score > best_score: best_name = name best_score = scoreprint(best_name, best_score)Ответ: Марк 95
Напиши программу: первые 10 чисел Фибоначчи
Напиши программу, которая печатает в одну строку первые 10 чисел Фибоначчи (0, 1, 1, 2, …), используя только две переменные и обмен a, b = b, a + b. Запусти её и запиши, что она напечатала.
Запишите десять чисел через пробел.
Подсказка
Начни с a, b = 0, 1; на каждом шаге печатай a с end=" " и делай обмен.
Разбор
Например:
a, b = 0, 1for _ in range(10): print(a, end=" ") a, b = b, a + bОтвет: 0 1 1 2 3 5 8 13 21 34