7 уровней использования функции zip в Python
- Просмотры: 3128
- Категория: Python
- Создано: 16 февраля 2021
- Тэги:
В 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')] |
Как показано в примере выше, функция zip возвращает итератор на основе кортежа, где i-й кортеж содержит i-е элементы из каждого списка.
Эта функция напоминает работу застёжки «молнии», не правда ли?
Уровень 1 . Меньше-больше итерируемых объектов за один раз
Фактически, функция zip в Python намного мощнее обычной застёжки-«молнии». Она может работать с любым количеством итерируемых объектов за один раз, а не только с двумя.
Если мы передадим один список в функцию zip, получим
id = [1, 2, 3, 4] | |
record = zip(id) | |
print(list(record)) | |
# [(1,), (2,), (3,), (4,)] |
А как насчёт трёх списков?
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')] |
Как мы указывали выше, не имеет значение, сколько итерируемых объектов мы передаём в функцию 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')] |
Как показано в коде выше, самый короткий список – это 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')] |
Как говорилось выше, результат функции 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') |
В приведённом выше примере звёздочка выполнят операцию распаковывания: она распаковывает все четыре кортежа из списка 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') |
Уровень 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'} |
Указанный пример выше вообще не использует цикл 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! |
Есть ли более элегантный метод решить эту задачу?
Уровень 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]] |
Вывод
Функция zip в Python очень полезная и мощная. Используя её свойства, мы можем писать меньше кода и делать больше операций. «Делать большее с помощью меньшего» - это философия Python.
Мой перевод статьи Yang Zhou: 7 Levels of Using the Zip Function in Python