zip
Функция является встроенной функцией Python.Изучайте Python со стариком: легкий вход«Есть некое введение, но, учитывая, что книга является вводной по Python, оно не слишком глубокое. Однако это не означает, что эту функцию нельзя применять глубоко, особенно понимание этой функции в сочетании с итераторами заставит людей почувствовать себя внезапно просветленными. В то же время некоторые проблемы можно решить гениально.
В этой статье делается попыткаzip
Совершите глубокое погружение, принимая во внимание ваше понимание итераторов.
Во-первых, разберитесь с функцией zip
См. операцию ниже.
>>> list(zip([1, 2, 3], ['a', 'b', 'c']))
[(1, 'a'), (2, 'b'), (3, 'c')]
[1, 2, 3]
и['a', 'b', 'c']
является списком, и все списки повторяемы. Это означает, что один элемент может быть возвращен за раз.
Функция zip завершается объектом zip — объектом итератора. И этот объект итератора состоит из нескольких кортежей.
Так как же генерируются эти кортежи?
Сравните приведенный выше код и начните считать с самого начала. По техническим привычкам в Python кортеж в начале считается с 0, то есть 0-й.
- Элемент с индексом 0 в 0-м кортеже происходит из 0-го параметра функции zip (
[1,2,3]
) Значение, на которое указывает текущий указатель, равно 1 в начале чтения; - Элемент с индексом 1 в 0-м кортеже происходит из 1-го параметра функции zip (
['a', 'b', 'c']
) Значение, на которое указывает текущий указатель, который также только что начал читаться, должно быть a; поэтому оно формирует 0-й кортеж zip-объекта(1, 'a')
. - Элемент с индексом 2 в 0-м кортеже происходит из второго параметра в zip-функции — второго нет, а вышеуказанные параметры имеют в сумме 0 и 1. Тогда кортеж формируется, а элемента с индексом 2 нет. Затем сформируйте следующий кортеж, который должен быть первым кортежем в соответствии с порядком подсчета здесь.
- Элемент с индексом 0 в первом кортеже объекта zip происходит от значения, на которое указывает текущий указатель 0-го параметра функции zip. Обратите внимание, что поскольку последний раз прошел 1, соответствующее значение в этот момент равно 2.
- Так же, как и выше, значение, на которое указывает указатель в это время, равно «b». Так формируется первый кортеж zip-объекта
(2, 'b')
. - Повторите это, чтобы получить окончательный результат.
Обратите внимание на приведенное выше утверждение.Если источник составляющих объектов в кортеже можно обобщить одним предложением, то это:i-й элемент в кортеже происходит из объекта элемента, на который в данный момент указывает указатель в i-м параметре.——Пожалуйста, внимательно вникните в смысл этого предложения, за ним стоит большая польза.
заzip
Функция, вышеописанный процесс, выглядит как "сжатие", то есть обратный процесс - распаковка. Например, исходные два объекта восстанавливаются из результатов приведенного выше примера.
имеют. Обычно считается, что это делается так:
>>> result = zip([1, 2, 3], ["a", "b", "c"])
>>> c, v = zip(*result)
>>> print(c, v)
(1, 2, 3) ('a', 'b', 'c')
Каков принцип.
result
Приложение является объектом-итератором, но для ясности его также можно понимать как[(1, 'a'), (2, 'b'), (3, 'c')]
. Следующий использованныйzip(*result)
, где обозначение*
Функция заключается в сборе параметров, которые подробно объясняются в параметрах функции (см. «Изучение Python на старом языке: легкий старт»).
>>> def foo(*a): print(a)
...
>>> lst = [(1,2), (3,4)]
>>> foo(*lst)
((1, 2), (3, 4))
>>> foo((1,2), (3,4))
((1, 2), (3, 4))
Следуя этому примеру, становится ясно, что следующие две операции эквивалентны.
>>> lst = [(1, 'a'), (2, 'b'), (3, 'c')]
>>> zip(*lst)
<zip object at 0x104c8fb48>
>>> zip((1, 'a'), (2, 'b'), (3, 'c'))
<zip object at 0x104f27308>
Из возвращаемой кодировки памяти объекта также видно, что это один и тот же объект.
В этом случае мы можем понятьzip((1, 'a'), (2, 'b'), (3, 'c'))
процесс создания результатов, чтобы понятьzip(*lst)
. Процесс генерации результата первого был описан выше и здесь повторяться не будет.
Получается, что так называемая «декомпрессия» и «сжатие» рассчитываются одинаково. вдруг увидеть свет.
Другие общие функции
Кромеzip
функция и встроенная функцияiter
, который принимает итерируемый объект и возвращает объект итератора.
>>> iter([1, 2, 3, 4])
<list_iterator object at 0x7fa80af0d898>
По сути,iter
Каждый элемент аргумента вызова функции, затем с помощью__next__
Функция возвращает объект итератора и объединяет результаты в кортеж.
встроенная функцияmap
— это еще одна функция, возвращающая объект-итератор, который принимает объект-функцию только с одним параметром в качестве параметра, и эта функция за раз берет один элемент из итерируемого объекта.
>>> list(map(len, ['abc', 'de', 'fghi']))
[3, 2, 4]
map
Принцип выполнения функции:__iter__
Функция вызывается со вторым аргументом и вызывается с__next__
Функция возвращает результат выполнения. В приведенном выше примереlen
Функция вызывается для каждого элемента в следующем списке и возвращает объект итератора.
Поскольку итераторы являются итерируемыми, мы можем положитьzip
Возвращенный объект итератора используетсяmap
параметры функции. Например, вычислите сумму соответствующих элементов в двух списках следующим образом.
>>> list(map(sum, zip([1, 2, 3], [4, 5, 6])))
[5, 7, 9]
Объекты Iterator имеют два основных эффекта: один из них — экономия памяти, но повышение эффективности выполнения.
Типичные проблемы
Существует список, состоящий из некоторых положительных целых чисел, таких как[1, 2, 3, 4, 5, 6]
, написать функцию, один параметр функцииn
Указывает, что несколько элементов в списке должны быть сгруппированы в группу, предполагая, что n=2, затем два элемента сгруппированы в группу и, наконец, возвращены[(1, 2), (3, 4), (5, 6)]
.
Упрощенно эту функцию можно записать так:
def naive_grouper(inputs, n):
num_groups = len(inputs) // n
return [tuple(inputs[i*n:(i+1)*n]) for i in range(num_groups)]
Проверьте, эта функция работает так, как мы хотим.
>>> nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> naive_grouper(nums, 2)
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]
Однако в приведенном выше тесте количество входящих элементов списка относительно невелико, если количество элементов списка велико, например, 1 миллион. Для этого требуется относительно большой объем памяти, иначе операция не может быть выполнена.
Однако, если программа выполняется так:
def naive_grouper(inputs, n):
num_groups = len(inputs) // n
return [tuple(inputs[i*n:(i+1)*n]) for i in range(num_groups)]
for _ in naive_grouper(range(100000000), 10):
pass
Сохраните вышеуказанную программу как файлnaive.py
. Вы можете использовать следующие команды для измерения занимаемого места в памяти и времени, затраченного на выполнение программы. Обратите внимание: убедитесь, что на вашем локальном компьютере установлено не менее 5 ГБ памяти.
$ time -f "Memory used (kB): %M\nUser time (seconds): %U" python3 naive.py
Memory used (kB): 4551872
User time (seconds): 11.04
Примечание. В системах Ubuntu вы можете выполнить/usr/bin/time
.
Передать в список или элементnaïve_grouper
Функция требует, чтобы компьютер предоставил 4,5 ГБ памяти для выполнения.range(100000000)
цикл.
Если вы используете объекты итераторов, произойдут большие изменения.
def better_grouper(inputs, n):
iters = [iter(inputs)] * n
return zip(*iters)
В этой короткой функции коннотация по-прежнему очень богата. Итак, мы собираемся объяснить построчно.
выражение[iters(inputs)] * n
Создает объект итератора, который содержит n идентичных объектов списка.
Ниже сn=2
Например.
>>> nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> iters = [iter(nums)] * 2
>>> list(id(itr) for itr in iters) # 内存地址是一样的
[139949748267160, 139949748267160]
существуетiters
Два объекта итератора являются одним и тем же объектом — это важно понимать.
комбинированная лицевая облицовкаzip
понимание,zip(*iters)
иzip(iter(nums), iter(nums))
это то же самое. Чтобы иметь возможность объяснить более интуитивно, его можно рассматривать какzip((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), (1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
. как описано ранееzip
Рабочий процесс, процесс его расчета выглядит следующим образом:
- 0-й элемент в 0-м кортеже из объекта, на который в данный момент указывает указатель в 0-м параметре, равен 1;
- Первый элемент в 0-м кортеже происходит от объекта, на который в настоящее время указывает указатель в первом параметре.Обратите внимание, что два параметра являются одним и тем же объектом, а указатель в это время ссылается на 2.
- 2-й элемент в 0-м кортеже, со 2-го аргумента - ничего. Итак, я получил кортеж
(1, 2)
. - Далее идет 0-й элемент в первом кортеже из объекта, на который в данный момент указывает указатель в 0-м параметре, или из-за элементов того же объекта, на этот раз указатель указывает на 3.
- И так далее, чтобы получить такие кортежи, как (3,4).
>>> nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(better_grouper(nums, 2))
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]
функция вышеbetter_grouper()
Преимущества:
- Не используйте встроенные функции
len()
, может принимать любую итерацию в качестве параметра - Он возвращает итерируемый объект, а не список. Занимает меньше памяти
Сохраните описанный выше процесс как файлbetter.py
.
def better_grouper(inputs, n):
iters = [iter(inputs)] * n
return zip(*iters)
for _ in better_grouper(range(100000000), 10):
pass
затем используйтеtime
Выполнить в терминале.
$ time -f "Memory used (kB): %M\nUser time (seconds): %U" python3 better.py
Memory used (kB): 7224
User time (seconds): 2.48
Сравните с предыдущим исполнениемnaive.py
, как в памяти, так и во времени выполнения, работают очень хорошо.
дальнейшие исследования
для вышеперечисленногоbetter_grouper
функцию, тщательно проанализируйте ее и обнаружите, что у нее все еще есть проблемы. Он может делить только числа в списке, которые делятся на длину списка, и если он не делится, некоторые элементы будут отброшены. Например:
>>> nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(better_grouper(nums, 4))
[(1, 2, 3, 4), (5, 6, 7, 8)]
Если вам нужна группа из 4 элементов, 9 и 10 элементов не могут быть сгруппированы. по кочануzip
функция.
>>> list(zip([1, 2, 3], ['a', 'b', 'c', 'd']))
[(1, 'a'), (2, 'b'), (3, 'c')]
Количество кортежей в конечном zip-объекте определяется объектом с наименьшей длиной в параметрах.
Если вам неудобно это делать, вы можете использоватьitertools.zip_longest()
, эта функция основана на самом длинном параметре, если его недостаточно, используется значение по умолчаниюNone
Fill, конечно же, тоже может передавать параметрыfillvalue
Определяет объект заливки.
>>> import itertools
>>> x = [1, 2, 3]
>>> y = ["a", "b", "c", "d"]
>>> list(itertools.zip_longest(x, y))
[(1, 'a'), (2, 'b'), (3, 'c'), (None, 'd')]
Тогда ты можешьbetter_grouper
в функцииzip
использоватьzip_longest()
заменены.
import itertools as it
def grouper(inputs, n, fillvalue=None):
iters = [iter(inputs)] * n
return it.zip_longest(*iters, fillvalue=fillvalue)
Запустите еще раз, и вот результат.
>>> nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> print(list(grouper(nums, 4)))
[(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, None, None)]