7 уровней использования функции zip в Python
Мастерство программирования - качество, которое отличает одного кандидата от другого на собеседовании. Вот почему мы уделяем этому вопросу очень пристальное внимание. Элегантность конструкций, краткость и лаконичность кода иллюстрируют мастерство и опыт программиста.
В социальных сетях IT Resume мы уже рассматривали довольно много примеров того, как улучшить свой код на Python, как работать со строками в Python, обозревали основные функции библиотек и многое другое. А сегодня целую статью посвятим не всем известной, но очень полезной функции - zip
.
Функция
zip
- функция в Python, которая объединяет элементы из разных итерируемых объектов, таких как списки, кортежи или наборы, и возвращает итератор.
Эта функция может сделать код очень элегантным. Однако ее использование не очень интуитивно понятно для новичков, а иногда и вызывает ошибки.
Например, у нас есть матрица 2*3
, представленная вложенным списком:
matrix = [[1, 2, 3], [1, 2, 3]]
А вот и популярный вопрос на собеседовании по Python:
Как получить транспонирование указанной выше матрицы?
Junior разработчик может написать несколько циклов for для его реализации, но Senior разработчику нужна всего одна строка кода:
matrix_T = [list(i) for I in zip(*matrix)]
Элегантно, не правда ли?
Если вы еще не поняли вышеупомянутое решение, не беспокойтесь. Эта статья подробно расскажет о концепциях и использовании мощной функции 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')]
Теперь ни Bill Gates, ни Yang Zhou не расстроятся. Необязательный аргумент fillvalue
может помочь нам заполнить недостающие значения. Значение по умолчанию аргумента равно None
, но мы можем поставить туда все, что угодно! Еще неизвестно, что было обиднее: не попасть в список record
или получить рядом со своим именем что-нибудь эдакое :)
Уровень 3: Операция распаковки
В предыдущем примере, если мы сначала получаем список record
, как мы можем разархивировать его на отдельные объекты?
К сожалению, в Python нет функции распаковки. Однако, если мы знакомы с уловками со звездочками, разархивирование - очень простая задача.
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
Это распространенный сценарий, когда одновременно обрабатывается несколько итерируемых объектов.
Проиллюстрируем это на следующем примере:
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
, распаковкой с помощью одной звездочки и dict 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!Эпилог
Функция zip
в Python очень полезна и мощна. Его правильное использование может помочь нам писать меньше кода и выполнять больше операций.
Не зря же «Делай больше меньшими средствами» - это философия Python.