7 уровней использования функции zip в Python

Просмотры: 3128
Категория: Python
Создано: 16 февраля 2021
Тэги: Python

В Python есть несколько встроенных функций, которые могут сделать наш код довольно элегантным. Одна из них – функция zip. Однако, использование этой функции не совсем понятно для начинающих и при её использовании они часто совершают ошибки.

Например, пусть имеется 2х3 матрица, представленная в виде вложенного списка:


matrix = [[1, 2, 3], [1, 2, 3]]

Популярный вопрос на собеседованиях по Python

Как транспонировать эту матрицу?

Джуниор разработчики могут написать для этого несколько циклов for. Однако сениор разработчику для этого понадобится одна строчка кода:


matrix_T = [list(i) for i in zip(*matrix)]

Элегантно, не правда ли?

Если вы пока не понимаете приведённое выше однострочное решение, не переживайте. Эта статья далее объяснит данный код. Мы также рассмотрим  на 7 уровнях концепцию, применение и советы по использованию мощной функции zip.

Если вы уже знакомы с приведённым выше решением, но хотите узнать другие замечательные приёмы использования функции zip, то эта статья – ваша чашка чая.

Уровень 0: Базовое использования функции Zip

Функция zip объединяет элементы различных итерируемых объектов, таких как списки, кортежи или множеств, и возвращает итератор.

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

id = [1, 2, 3, 4]
leaders = ['Elon Mask', 'Tim Cook', 'Bill Gates', 'Yang Zhou']
record = zip(id, leaders)
print(record)
# <zip object at 0x7f266a707d80>
print(list(record))
# [(1, 'Elon Mask'), (2, 'Tim Cook'), (3, 'Bill Gates'), (4, 'Yang Zhou')]
view raw zip1.py hosted with ❤ by GitHub

Как показано в примере выше, функция zip возвращает итератор на основе кортежа, где i-й кортеж содержит i-е элементы из каждого списка.

Эта функция напоминает работу застёжки «молнии», не правда ли?

Уровень 1 . Меньше-больше итерируемых объектов за один раз

Фактически, функция zip в Python намного мощнее обычной застёжки-«молнии». Она может работать с любым количеством итерируемых объектов за один раз, а не только с двумя.

Если мы передадим один список в функцию zip, получим

id = [1, 2, 3, 4]
record = zip(id)
print(list(record))
# [(1,), (2,), (3,), (4,)]
view raw zip2.py hosted with ❤ by GitHub

А как насчёт трёх списков?

id = [1, 2, 3, 4]
leaders = ['Elon Mask', 'Tim Cook', 'Bill Gates', 'Yang Zhou']
sex = ['male', 'male', 'male', 'male']
record = zip(id, leaders, sex)
print(list(record))
# [(1, 'Elon Mask', 'male'), (2, 'Tim Cook', 'male'), (3, 'Bill Gates', 'male'), (4, 'Yang Zhou', 'male')]
view raw zip3.py hosted with ❤ by GitHub

Как мы указывали выше, не имеет значение, сколько итерируемых объектов мы передаём в функцию zip, она просто работает так, как от неё и ожидается.

Между прочем, если не передать ни одного аргумента в zip функцию, она просто вернёт пустой итератор.

Уровень 2. Работаем с неравными по длине аргументами

Реальные данные не всегда чистые и полные, иногда мы вынуждены работать с неравными по длине итерируемыми объектами. По умолчанию, результат работы zip функции основан на самом коротком итерируемом объекте.

id = [1, 2]
leaders = ['Elon Mask', 'Tim Cook', 'Bill Gates', 'Yang Zhou']
record = zip(id, leaders)
print(list(record))
# [(1, 'Elon Mask'), (2, 'Tim Cook')]
view raw zip4.py hosted with ❤ by GitHub

Как показано в коде выше, самый короткий список – это id, поэтому record содержит только два кортежа, а последние два лидера в списке leaders были проигнорированы.

Но что делать, если последние два лидера обидятся на нас, если мы их проигнорируем?

Python снова поможет нам. Существует другая функция в модуле itertools, которая называется zip_longest. Как говорит её имя, она является сестрой функции zip, а её результат основывается на самом длинном аргументе.

Давайте применим zip_longest для того, чтобы сгенерировать список record

from itertools import zip_longest
id = [1, 2]
leaders = ['Elon Mask', 'Tim Cook', 'Bill Gates', 'Yang Zhou']
long_record = zip_longest(id, leaders)
print(list(long_record))
# [(1, 'Elon Mask'), (2, 'Tim Cook'), (None, 'Bill Gates'), (None, 'Yang Zhou')]
long_record_2 = zip_longest(id, leaders, fillvalue='Top')
print(list(long_record_2))
# [(1, 'Elon Mask'), (2, 'Tim Cook'), ('Top', 'Bill Gates'), ('Top', 'Yang Zhou')]
view raw zip5.py hosted with ❤ by GitHub

Как говорилось выше, результат функции zip_longest основывается на самом длинном аргументе. Опциональный аргумент fillvalue, у которого значение по умолчанию None, может помочь нам заполнить потерянные значения.

Уровень 3. Обратная операция unzip

Пусть, как в предыдущем примере, мы сначала получили список record. Как мы можем раззиповать его по отдельным итерируемым объектам?

К сожалению, в Python нет функции unzip. Однако, если мы знакомы с приёмам работы со звёздочкой, «раззипирование» становится очень лёгкой задачей.

record = [(1, 'Elon Mask'), (2, 'Tim Cook'), (3, 'Bill Gates'), (4, 'Yang Zhou')]
id, leaders = zip(*record)
print(id)
# (1, 2, 3, 4)
print(leaders)
# ('Elon Mask', 'Tim Cook', 'Bill Gates', 'Yang Zhou')
view raw zip6.py hosted with ❤ by GitHub

В приведённом выше примере звёздочка выполнят операцию распаковывания: она распаковывает все четыре кортежа из списка record.

Если мы не хотим использовать «звёздную» технику, то тогда мы должны действовать так.

record = [(1, 'Elon Mask'), (2, 'Tim Cook'), (3, 'Bill Gates'), (4, 'Yang Zhou')]
print(*record) # unpack the list by one asterisk
# (1, 'Elon Mask') (2, 'Tim Cook') (3, 'Bill Gates') (4, 'Yang Zhou')
id, leaders = zip((1, 'Elon Mask'), (2, 'Tim Cook'), (3, 'Bill Gates'), (4, 'Yang Zhou'))
print(id)
# (1, 2, 3, 4)
print(leaders)
# ('Elon Mask', 'Tim Cook', 'Bill Gates', 'Yang Zhou')
view raw zip7.py hosted with ❤ by GitHub
Уровень 4. Создание и обновление словарей с помощью функции zip

С помощью функции zip очень легко создавать или обновлять на основе отдельных списков словари. Здесь имеются два однострочных решения:

  • Использование dict comprehension и zip
  • Использование функции dict и zip
id = [1, 2, 3, 4]
leaders = ['Elon Mask', 'Tim Cook', 'Bill Gates', 'Yang Zhou']
# create dict by dict comprehension
leader_dict = {i: name for i, name in zip(id, leaders)}
print(leader_dict)
# {1: 'Elon Mask', 2: 'Tim Cook', 3: 'Bill Gates', 4: 'Yang Zhou'}
# create dict by dict function
leader_dict_2 = dict(zip(id, leaders))
print(leader_dict_2)
# {1: 'Elon Mask', 2: 'Tim Cook', 3: 'Bill Gates', 4: 'Yang Zhou'}
# update
other_id = [5, 6]
other_leaders = ['Larry Page', 'Sergey Brin']
leader_dict.update(zip(other_id, other_leaders))
print(leader_dict)
# {1: 'Elon Mask', 2: 'Tim Cook', 3: 'Bill Gates', 4: 'Yang Zhou', 5: 'Larry Page', 6: 'Sergey Brin'}
view raw zip8.py hosted with ❤ by GitHub

Указанный пример выше вообще не использует цикл for. Как это элегантно и «питонистично»!

Уровень 5. Использование функции zip в циклах for

Часто приходится обрабатывать много итерируемых объектов за один раз. Здесь функция zip вместе с циклом for может прийти нам на помощь.

Давайте рассмотрим следующий пример:

products = ["cherry", "strawberry", "banana"]
price = [2.5, 3, 5]
cost = [1, 1.5, 2]
for prod, p, c in zip(products, price, cost):
print(f'The profit of a box of {prod} is £{p-c}!')
# The profit of a box of cherry is £1.5!
# The profit of a box of strawberry is £1.5!
# The profit of a box of banana is £3!
view raw zip9.py hosted with ❤ by GitHub

Есть ли более элегантный метод решить эту задачу?

Уровень 6. Получить транспонированную матрицу

Наконец, возвращаемся к вопросу из собеседования по Python

Как получить транспонированную матрицу?

Так как мы уже знаем функцию zip, распаковывание с помощью одинарной звёздочки, и list comprehension, то однострочное решение становится почти очевидным

matrix = [[1, 2, 3], [1, 2, 3]]
matrix_T = [list(i) for i in zip(*matrix)]
print(matrix_T)
# [[1, 1], [2, 2], [3, 3]]
view raw zip10.py hosted with ❤ by GitHub
Вывод

Функция zip в Python очень полезная и мощная. Используя её свойства, мы можем писать меньше кода и делать больше операций. «Делать большее с помощью меньшего» - это философия Python.

Мой перевод статьи Yang Zhou: 7 Levels of Using the Zip Function in Python