Как zip (* [iter (s)] * n) работает в Python?

103
s = [1,2,3,4,5,6,7,8,9]
n = 3

zip(*[iter(s)]*n) # returns [(1,2,3),(4,5,6),(7,8,9)]

Как zip(*[iter(s)]*n)работает? Как бы он выглядел, если бы был написан с более подробным кодом?

Оливер Чжэн
источник
1
также посмотрите здесь, где также объясняется, как это работает: stackoverflow.com/questions/2202461/…
Мэтт Джоунер,
если ответов здесь недостаточно, я написал об этом здесь: telliott99.blogspot.com/2010/01/…
telliott99
7
Хотя этот метод очень интригующий, он должен противоречить основному значению «читабельности» Python!
Демис

Ответы:

109

iter()является итератором по последовательности. [x] * nсоздает список, содержащий nколичество x, т.е. список длины n, в котором находится каждый элемент x. *argраспаковывает последовательность в аргументы для вызова функции. Поэтому вы передаете один и тот же итератор 3 раза zip(), и он каждый раз извлекает элемент из итератора.

x = iter([1,2,3,4,5,6,7,8,9])
print zip(x, x, x)
Игнасио Васкес-Абрамс
источник
1
Полезно знать: когда итератор использует yield(= returns) элемент, вы можете представить этот элемент как «потребленный». Таким образом, при следующем вызове итератора он возвращает следующий «неиспользованный» элемент.
winklerrr
46

Другие отличные ответы и комментарии хорошо объясняют роль распаковки аргументов и zip () .

Как говорят Игнасио и Удзюкацель , вы переходите к zip()трем ссылкам на один и тот же итератор и создаете тройки zip()целых чисел - по порядку - из каждой ссылки на итератор:

1,2,3,4,5,6,7,8,9  1,2,3,4,5,6,7,8,9  1,2,3,4,5,6,7,8,9
^                    ^                    ^            
      ^                    ^                    ^
            ^                    ^                    ^

И поскольку вы просите более подробный пример кода:

chunk_size = 3
L = [1,2,3,4,5,6,7,8,9]

# iterate over L in steps of 3
for start in range(0,len(L),chunk_size): # xrange() in 2.x; range() in 3.x
    end = start + chunk_size
    print L[start:end] # three-item chunks

Следуя значениям startи end:

[0:3) #[1,2,3]
[3:6) #[4,5,6]
[6:9) #[7,8,9]

FWIW, вы можете получить тот же результат с map()начальным аргументом None:

>>> map(None,*[iter(s)]*3)
[(1, 2, 3), (4, 5, 6), (7, 8, 9)]

Для получения дополнительной информации zip()и map(): http://muffinresearch.co.uk/archives/2007/10/16/python-transposing-lists-with-map-and-zip/

механическое_ мясо
источник
31

Я думаю, что одна вещь, упущенная во всех ответах (вероятно, очевидная для тех, кто знаком с итераторами), но не столь очевидная для других, - это -

Поскольку у нас один и тот же итератор, он потребляется, а остальные элементы используются архивом. Итак, если бы мы просто использовали список, а не его, например.

l = range(9)
zip(*([l]*3)) # note: not an iter here, the lists are not emptied as we iterate 
# output 
[(0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5), (6, 6, 6), (7, 7, 7), (8, 8, 8)]

Используя итератор, выталкивает значения и сохраняет только оставшиеся доступные, поэтому для zip после использования 0 доступно 1, затем 2 и так далее. Очень тонкая вещь, но довольно умная !!!

габхиджит
источник
+1, ты меня спас! Я не могу поверить, что в других ответах эта важная деталь была пропущена, если все это знают. Можете ли вы дать ссылку на документацию, которая включает эту информацию?
Снехасиш Кармакар
9

iter(s) возвращает итератор для s.

[iter(s)]*n составляет список n раз один и тот же итератор для s.

Таким образом, при выполнении zip(*[iter(s)]*n)он по порядку извлекает элемент из всех трех итераторов из списка. Поскольку все итераторы являются одним и тем же объектом, он просто группирует список по частям n.

Sttwister
источник
7
Не n итераторов одного и того же списка, но n раз один и тот же объект итератора. Различные объекты итератора не имеют общего состояния, даже если они находятся в одном списке.
Thomas Wouters
Спасибо, поправили. Действительно, я «думал» об этом, но написал кое-что еще.
sttwister
6

Один совет по использованию zip таким образом. Он обрежет ваш список, если его длина не делится поровну. Чтобы обойти это, вы можете использовать itertools.izip_longest, если вы можете принимать значения заливки. Или вы можете использовать что-то вроде этого:

def n_split(iterable, n):
    num_extra = len(iterable) % n
    zipped = zip(*[iter(iterable)] * n)
    return zipped if not num_extra else zipped + [iterable[-num_extra:], ]

Использование:

for ints in n_split(range(1,12), 3):
    print ', '.join([str(i) for i in ints])

Печать:

1, 2, 3
4, 5, 6
7, 8, 9
10, 11
Jmagnusson
источник
3
Это уже задокументировано в itertoolsрецептах: docs.python.org/2/library/itertools.html#recipes grouper . Не нужно изобретать велосипед
джамылак
1

Вероятно, легче увидеть, что происходит в интерпретаторе python или ipythonс помощью n = 2:

In [35]: [iter("ABCDEFGH")]*2
Out[35]: [<iterator at 0x6be4128>, <iterator at 0x6be4128>]

Итак, у нас есть список из двух итераторов, которые указывают на один и тот же объект итератора. Помните, что iterобъект возвращает объект-итератор, и в этом сценарии это тот же итератор дважды из-за *2синтаксического сахара Python. Итераторы также запускаются только один раз.

Кроме того, zipпринимает любое количество итераций ( последовательности являются итерациями ) и создает кортеж из i-го элемента каждой из входных последовательностей. Поскольку в нашем случае оба итератора идентичны, zip перемещает один и тот же итератор дважды для каждого двухэлементного кортежа вывода.

In [41]: help(zip)
Help on built-in function zip in module __builtin__:

zip(...)
    zip(seq1 [, seq2 [...]]) -> [(seq1[0], seq2[0] ...), (...)]

    Return a list of tuples, where each tuple contains the i-th element
    from each of the argument sequences.  The returned list is truncated
    in length to the length of the shortest argument sequence.

Оператор unpacking ( *) гарантирует, что итераторы работают до полного исчерпания, что в данном случае происходит до тех пор, пока не будет достаточно ввода для создания двухэлементного кортежа.

Это может быть расширено до любого значения nи zip(*[iter(s)]*n)работает, как описано.

Ахан
источник
Извините за медлительность. Но не могли бы вы объяснить «один и тот же итератор дважды из-за синтаксического сахара Python * 2. Итераторы также запускаются только один раз». расстаться пожалуйста? Если да, то почему результат не [(«А», «А») ....]? Спасибо.
Боуэн Лю
@BowenLiu *- это просто удобство дублирования объекта. Попробуйте это со скалярами, а затем со списками. Также попробуйте print(*zip(*[iter("ABCDEFG")]*2))против print(*zip(*[iter("ABCDEFG"), iter("ABCDEFG")])). Затем начните разбивать их на более мелкие шаги, чтобы увидеть, каковы на самом деле объекты-итераторы в двух операторах.
Ахан