К содержанию

Модуль 4 · Урок 4.3

Кортежи, распаковка, enumerate и zip

Небольшие неизменяемые записи, раздача элементов по именам и совместный перебор.

7 задач

Результат ученика — это пара «имя и балл», точка на плоскости — пара координат. Такие короткие записи не должны случайно меняться. Для них есть кортеж — неизменяемая последовательность, а распаковка раздаёт его элементы по именам одним присваиванием. Распаковка работает и для списков из прошлого урока.

Данные урока — результаты учеников парами «имя и балл»: ("Анна", 91), ("Илья", 84), ("Марк", 95).

Кортеж

Кортеж похож на список, но его нельзя изменить. У кортежа (tuple) нельзя заменить элемент, добавить или удалить позицию. Он подходит для небольшой записи: координат, пары «имя и результат», нескольких возвращаемых значений.

point = (3, 5)print(point[0])single = (8,)empty = ()print(type((8)), type(single), len(empty))
3 <class 'int'> <class 'tuple'> 0

Кортеж создаёт запятая, а не только круглые скобки. (8) — просто число в скобках, (8,) — кортеж из одного элемента. Попытка заменить элемент остановит программу:

point = (3, 5)point[0] = 4
TypeError: 'tuple' object does not support item assignment
Тонкости:список внутри кортежа

Неизменяемость кортежа не делает неизменяемым список внутри него: в ([1, 2], 3) заменить список нельзя, а добавить в него элемент можно.

Распаковка

Распаковка связывает несколько имён с элементами последовательности.

point = (3, 5)x, y = pointprint(x, y) a = 10b = 20a, b = b, aprint(a, b)
3 5 20 10

Справа сначала вычисляются прежние значения b и a, затем выполняется присваивание. Обычные последовательные строки a = b и b = a обмена не дадут: прежнее a будет потеряно — после них оба имени будут равны 20.

После a, b = b, a: правая часть уже вычислена как пара (20, 10), затем имена связаны с её элементами. Прежние связи — пунктиром; сами числа никуда не копировались.

Количество значений должно совпадать с количеством имён:

x, y = (1, 2, 3)
ValueError: too many values to unpack (expected 2, got 3)

Можно обозначить имя для остальных элементов звёздочкой:

first, *middle, last = [10, 20, 30, 40]print(first, middle, last)
10 [20, 30] 40

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)
1 Анна 2 Илья 3 Марк

start=1 меняет выдаваемые номера, но не индексы исходного списка. В каждой итерации пара распаковывается в два имени.

zip соединяет несколько источников по позициям:

names = ["Анна", "Илья", "Марк"]scores = [91, 84]for name, score in zip(names, scores):    print(name, score)
Анна 91 Илья 84

Балла Марка в списке нет, и zip закончился на коротком списке: третьей пары не будет, а программа не сообщит об этом.

Слева enumerate ставит перед каждым именем номер. Справа zip собирает пары по позициям: первое имя с первым баллом, второе со вторым; Марку пары не досталось.
Тонкости:zip без потерь

Если длины по смыслу обязаны совпадать, пиши zip(names, scores, strict=True): при разной длине программа остановится с ValueError, и потеря данных не пройдёт незаметно.

Звёздочка в вызове функции означает ещё одно применение распаковки:

numbers = [10, 20, 30]print(*numbers)print(*numbers, sep=", ")
10 20 30 10, 20, 30

Элементы передаются отдельными аргументами, как в 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 по отдельности.

Проверьте себя

Ответь сначала сам, потом открой ответ.

Вопрос 1

Чем (8) отличается от (8,)?

ОтветСкрыть ответ
Ответ

(8) — просто число 8 в скобках, (8,) — кортеж из одного элемента: кортеж создаёт запятая.

Вопрос 2

Почему строки a = b и b = a не меняют значения местами, а a, b = b, a меняет?

ОтветСкрыть ответ
Ответ

После a = b прежнее значение a потеряно. В a, b = b, a правая часть сначала целиком вычисляется в пару старых значений, и только потом имена связываются с её элементами.

Вопрос 3

Что напечатает цикл for a, b in zip([1, 2, 3], "xy"): print(a, b)?

ОтветСкрыть ответ
Ответ

Две строки: «1 x» и «2 y». zip закончился на короткой строке, тройке пары не досталось.

Вопрос 4

Что получат first и rest после first, *rest = "PYTHON"?

ОтветСкрыть ответ
Ответ

first — 'P', rest — ['Y', 'T', 'H', 'O', 'N'].

Вопрос 5

Что выведет цикл по enumerate(["a", "b", "c"], start=1)? Каков индекс "c" в самом списке?

ОтветСкрыть ответ
Ответ

1 a, 2 b, 3 c; в списке у "c" индекс 2.

Практика

Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.

Напиши программу: ученики с баллом от 90

Есть список пар [("Оля", 77), ("Петя", 95), ("Вера", 90)]. Напиши программу, которая печатает имя и балл тех, у кого результат не меньше 90, распаковывая пары в заголовке for. Запусти её и запиши, что она напечатала.

Запишите напечатанные строки через пробел в порядке вывода.

Что напечатает программа?

print(type((8)).__name__)print(type((8,)).__name__)print(len((8,)))

Запишите короткие имена типов и число через пробел в порядке вывода, без <class …>.

Что напечатает программа?

a, b = 1, 2a, b = b, a + ba, b = b, a + ba, b = b, a + bprint(a, b)

Запишите напечатанные числа через пробел.

Что напечатает программа?

count = 0for pair in zip([1, 2, 3], "ab"):    count += 1print(count)

Введите число.

Что напечатает программа?

print(*[3, 1, 4], sep=", ")

Запишите напечатанную строку целиком.

Напиши программу: лучший ученик через zip

Имена ["Анна", "Илья", "Марк"] и баллы [91, 84, 95] лежат в двух списках. Напиши программу, которая проходит их через zip и печатает имя и балл лучшего ученика. Запусти её и запиши ответ.

Запишите напечатанную строку: имя и балл через пробел.

Напиши программу: первые 10 чисел Фибоначчи

Напиши программу, которая печатает в одну строку первые 10 чисел Фибоначчи (0, 1, 1, 2, …), используя только две переменные и обмен a, b = b, a + b. Запусти её и запиши, что она напечатала.

Запишите десять чисел через пробел.