Як 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!
Деміс

Відповіді:


108

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
Добре знати: коли ітератор yields (= returns) елемент, ви можете уявити цей елемент як "спожитий". Отже, наступного разу, коли викликається ітератор, він видає наступний "неспоживаний" елемент.
winklerrr

46

Інші чудові відповіді та коментарі добре пояснюють ролі розпакування аргументів та zip () .

Як кажуть Ігнасіо і урукацель , ви переходите до zip()трьох посилань на один ітератор і робитеzip() 3-кратні цілі числа - по порядку - з кожного посилання на ітератор:

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-transposed-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)]

Використовуючи ітератор, з'являється значення і залишається доступним лише тоді, коли для поштового індексу один раз 0 споживається 1, а потім 2 і так далі. Дуже тонка річ, але досить розумна !!!


+1, ти мене врятував! Я не можу повірити, що інші відповіді пропустили цю життєву деталь, припускаючи, що всі це знають. Чи можете ви надати будь-яку посилання на документацію, яка включає цю інформацію?
Snehasish Karmakar

9

iter(s) повертає ітератор для s.

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

Отже, виконуючи zip(*[iter(s)]*n)це, він витягує елемент зі всіх трьох ітераторів зі списку по порядку. Оскільки всі ітератори є одним і тим же об'єктом, він просто групує список за шматками n.


7
Не 'n ітераторів одного і того ж списку', але 'n разів однакового об’єкта ітератора'. Різні об'єкти ітератора не поділяють стан, навіть якщо вони є з одного списку.
Thomas Wouters

Спасибі, виправлено. Дійсно саме так я і думав, але писав щось інше.
sttwister

6

Одне слово поради щодо використання zip таким чином. Він уріже ваш список, якщо його довжина не розділиться рівномірно. Щоб вирішити це, ви можете використовувати itertools.izip_lo most, якщо ви можете прийняти значення заповнення. Або ви можете використовувати щось подібне:

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

3
Це вже задокументовано в itertoolsрецептах: docs.python.org/2/library/itertools.html#recipes grouper . Не потрібно винаходити колесо
jamylak

1

Напевно, простіше побачити, що відбувається в інтерпретаторі python або ipythonз n = 2:

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

Отже, у нас є список двох ітераторів, які вказують на той самий ітераторний об’єкт. Пам’ятайте, що iterна об’єкт повертається ітераторний об’єкт, і в цьому сценарії він є тим самим ітератором двічі через *2синтаксичний цукор пітона. Ітератори також працюють лише один раз.

Далі zipприймає будь-яку кількість ітерабелів ( послідовності є ітерабелями ) і створює кортеж з i-го елемента кожної з вхідних послідовностей. Оскільки обидва ітератора в нашому випадку однакові, zip переміщує один і той же ітератор двічі для кожного 2-елементного кордону виводу.

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.

Оператор розпакування ( *) забезпечує, що ітератори працюють до виснаження, що в цьому випадку є, поки не буде достатньо вводу для створення двоелементного кортежу.

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


Вибачте, що повільно. Але чи можете ви пояснити "той самий ітератор двічі через синтаксичний цукор * 2 пітона. Ітератори також працюють лише один раз". частина будь ласка? Якщо так, то як результат не [("А", "А") ....]? Дякую.
Боуен Лю

@BowenLiu *- це просто зручність дублювання об'єкта. Спробуйте це зі скалярами, а потім зі списками. Також спробуйте print(*zip(*[iter("ABCDEFG")]*2))проти print(*zip(*[iter("ABCDEFG"), iter("ABCDEFG")])). Тоді починайте розривати їх двома кроками, щоб побачити, що насправді є об'єктами ітератора у двох операторах.
акхан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.