Як 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.