№38 Функция zip() / для начинающих
Функция zip() в Python создает итератор, который объединяет элементы из нескольких источников данных. Эта функция работает со списками, кортежами, множествами и словарями для создания списков или кортежей, включающих все эти данные.
В Python есть несколько встроенных функций, которые позволяют перебирать данные. Одна из них — zip. Функция zip() в Python создает итератор, который объединяет элементы из нескольких источников данных.
У функции zip() множество сценариев применения. Например, она пригодится, если нужно создать набор словарей из двух массивов, каждый из которых содержит имя и номер сотрудника.
В этом материале разберемся с основами этой функции, познакомимся с отличиями в Python 2 и Python 3, а также узнаем, в каких ситуациях zip() может быть полезна.
Обновление итератора Python
В Python итерация — это процесс прохождения программы по списку. Например, если есть цикл for, который выводит название каждой сферы, которой занимается компания, можно сказать, что программа выполняет итерацию (перебирает) списка названий.
Итерируемый объект же — это объект, который может возвращать отдельные элементы. Таким образом массивы — это итерируемые объекты, потому что можно вывести каждый отдельный элемент с помощью цикла for. Основы итерации в Python вспомнили — теперь можно переходить и к функции zip() .
Пример работы функция zip() в Python
Функция zip() принимает итерируемый объект, например, список, кортеж, множество или словарь в качестве аргумента. Затем она генерирует список кортежей, которые содержат элементы из каждого объекта, переданного в функцию.
Предположим, что есть список имен и номером сотрудников, и их нужно объединить в массив кортежей. Для этого можно использовать функцию zip() . Вот пример программы, которая делает именно это:
employee_numbers = [2, 9, 18, 28]
employee_names = ["Дима", "Марина", "Андрей", "Никита"]
zipped_values = zip(employee_names, employee_numbers)
zipped_list = list(zipped_values)
print(zipped_list)Функция zip возвращает следующее:
[('Дима', 2), ('Марина', 9), ('Андрей', 18), ('Никита', 28)]
Эта программа создала массив из кортежей, каждый из которых содержит имя и номер сотрудника. Здесь много составляющих, поэтому разберем по порядку. На первых двух строках объявляются переменные, которые хранят номера и имена сотрудников. На следующей — функция zip() объединяет два списка вместе, создавая новый массив кортежей.
На следующей строке zip-элемент конвертируется в список так, чтобы его можно было выводить и использовать как список. И на финальной строке он уже и выводится.
Можно убедиться в том, что zipped_values — это элемент zip() , воспользовавшись следующим кодом:
Разбираемся, как работает встроенная функция zip в Python, и пишем свою реализацию с помощью list comprehension
Многие слышали о функции zip в Python, а кто-то даже регулярно ей пользуется. Сегодня мы (из интереса и для общего развития) опишем, как можно реализовать её самому с помощью list comprehensions.
Для начала поясню, что вообще делает функция zip, для тех, кто с ней раньше не сталкивался:
>>> s = 'abc' >>> t = (10, 20, 30) >>> zip(s,t) [('a', 10), ('b', 20), ('c', 30)]
То есть функция берёт на вход несколько списков и создаёт из них список (в Python 3 создаётся не list, а специальный zip-объект) кортежей, такой, что первый элемент полученного списка содержит кортеж из первых элементов всех списков-аргументов. Таким образом, если ей передать три списка, то она отработает следующим образом:
>>> s = 'abc' >>> t = (10, 20, 30) >>> u = (-5, -10, -15) >>> list(zip(s,t,u)) [('a', 10, -5), ('b', 20, -10), ('c', 30, -15)]
В общем-то, функция отработает даже для одного iterable-объекта, результатом будет последовательность из кортежей, в каждом из которых будет по одному элементу. Но это, пожалуй, не самый распространенный способ применения zip. Я часто использую zip, например, для создания словарей:
>>> names = ['Tom', 'Dick', 'Harry'] >>> ages = [50, 35, 60] >>> dict(zip(names, ages))Это весьма удобно, не находите? Каждый раз, когда я рассказываю о zip на своих уроках, у меня спрашивают о том, что будет, если в функцию передать массивы разной длины. Ответ простой — победит более короткий:
>>> s = 'abc' >>> t = (10, 20, 30, 40) >>> list(zip(s,t)) [('a', 10), ('b', 20), ('c', 30)]
Однако, если вам необходимо, чтобы для каждого из элементов более длинного массива в результирующем списке был создан кортеж из одного элемента, вы можете использовать zip_longest из пакета itertools.
Есть одна возможность в Python, которая мне нравится даже больше, чем zip. Это списковое включение (англ. list comprehension). Именно поэтому, когда один из студентов недавно спросил меня, можем ли мы реализовать zip сами с помощью списковых включений, я просто не смог устоять.
Как же нам этого добиться? Начнём с первого, что приходит на ум:
[(s[i], t[i]) # создаём кортеж из двух элементов for i in range(len(s))] # для индексов от 0 до len(s) - 1
В общем-то всё! Это работает. Но есть несколько моментов, которые всё же стоит доработать в этом методе.
Во-первых, оригинальная функция могла работать с массивами разной длины. Поэтому вместо range(len(s)) нам стоит использовать range(len(x)) , где x — наиболее короткая последовательность. Для этого достаточно поместить все последовательности в один список, отсортировать этот список по длине элементов и выяснить длину элемента, оказавшегося под нулевым индексом:
>>> s = 'abcd' >>> t = (10, 20, 30) >>> sorted((s,t), key=len) [(10, 20, 30), 'abcd']
Совмещаем это с предыдущим кодом:
>>> s = 'abcd' >>> t = (10, 20, 30) >>> sorted((s,t), key=len) [(10, 20, 30), 'abcd']
Это ещё не все доработки, а выражение уже получается слишком длинным. Пожалуй, выяснение наименьшей длины стоит вынести в отдельную функцию (заодно сделаем так, чтобы она вычисляла наикратчайшую последовательность из неограниченного количества аргументов):
>>> def shortest_sequence_range(*args): return range(len(sorted(args, key=len)[0])) >>> [(s[i], t[i]) for i in shortest_sequence_range(s,t) ]
Что осталось теперь? Как уже говорилось выше, Python 3 создаёт не список, а специальный zip-объект, возвращая итератор от него. Это сделано для того, чтобы код не ломался при обработке исключительно длинных последовательностей. Это можно реализовать, но уже не с помощью спискового включения (которое всегда возвращает список), а с помощью генератора. К счастью, для этого достаточно поменять квадратные скобки на круглые:
>>> def shortest_sequence_range(*args): return range(len(sorted(args, key=len)[0])) >>> g = ((s[i], t[i]) for i in shortest_sequence_range(s,t) ) >>> for item in g: print(item) ('a', 10) ('b', 20) ('c', 30)
Готово! Мы реализовали свой полностью рабочий zip. Вы можете потренироваться и самостоятельно подумать, как ещё можно улучшить этот алгоритм.