【问题标题】:How does zip(*[iter(s)]*n) work in Python?zip(*[iter(s)]*n) 在 Python 中是如何工作的?
【发布时间】:2011-01-15 01:09:08
【问题描述】:
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) 是如何工作的?如果用更冗长的代码编写会是什么样子?

【问题讨论】:

标签: python iterator


【解决方案1】:

我需要分解每个部分步骤以真正内化它的工作原理。我的 REPL 笔记:

>>> # refresher on using list multiples to repeat item
>>> lst = list(range(15))
>>> lst
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> # lst id value
>>> id(lst)
139755081359872
>>> [id(x) for x in [lst]*3]
[139755081359872, 139755081359872, 139755081359872]

# replacing lst with an iterator of lst
# It's the same iterator three times
>>> [id(x) for x in [iter(lst)]*3 ]
[139755085005296, 139755085005296, 139755085005296]
# without starred expression zip would only see single n-item list.
>>> print([iter(lst)]*3)
[<list_iterator object at 0x7f1b440837c0>, <list_iterator object at 0x7f1b440837c0>, <list_iterator object at 0x7f1b440837c0>]
# Must use starred expression to expand n arguments
>>> print(*[iter(lst)]*3)
<list_iterator object at 0x7f1b4418b1f0> <list_iterator object at 0x7f1b4418b1f0> <list_iterator object at 0x7f1b4418b1f0>

# by repeating the same iterator, n-times,
# each pass of zip will call the same iterator.__next__() n times
# this is equivalent to manually calling __next__() until complete
>>> iter_lst = iter(lst)
>>> ((iter_lst.__next__(), iter_lst.__next__(), iter_lst.__next__()))
(0, 1, 2)
>>> ((iter_lst.__next__(), iter_lst.__next__(), iter_lst.__next__()))
(3, 4, 5)
>>> ((iter_lst.__next__(), iter_lst.__next__(), iter_lst.__next__()))
(6, 7, 8)
>>> ((iter_lst.__next__(), iter_lst.__next__(), iter_lst.__next__()))
(9, 10, 11)
>>> ((iter_lst.__next__(), iter_lst.__next__(), iter_lst.__next__()))
(12, 13, 14)
>>> ((iter_lst.__next__(), iter_lst.__next__(), iter_lst.__next__()))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

# all together now!
# continuing with same iterator multiple times in list
>>> print(*[iter(lst)]*3)
<list_iterator object at 0x7f1b4418b1f0> <list_iterator object at 0x7f1b4418b1f0> <list_iterator object at 0x7f1b4418b1f0>
>>> zip(*[iter(lst)]*3)
<zip object at 0x7f1b43f14e00>
>>> list(zip(*[iter(lst)]*3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14)]

# NOTE: must use list multiples. Explicit listing creates 3 unique iterators
>>> [iter(lst)]*3 == [iter(lst), iter(lst), iter(lst)]
False
>>> list(zip(*[[iter(lst), iter(lst), iter(lst)]))
[(0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), ....    

【讨论】:

    【解决方案2】:

    使用n = 2 可能更容易看到python 解释器或ipython 中发生的事情:

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

    所以,我们有一个包含两个迭代器的列表,它们指向同一个迭代器对象。请记住,对象上的iter 返回一个迭代器对象,在这种情况下,由于*2 python 语法糖,它是同一个迭代器两次。迭代器也只运行一次。

    此外,zip 接受任意数量的迭代(sequencesiterables)并从每个输入序列的第 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.
    

    unpacking (*) operator 确保迭代器耗尽,在这种情况下,直到没有足够的输入来创建一个 2 元素元组。

    这可以扩展到n 的任何值,zip(*[iter(s)]*n) 的工作方式与描述相同。

    【讨论】:

    • 抱歉有点慢。但是你能解释一下“由于 *2 python 语法糖,同一个迭代器两次。迭代器也只运行一次。”请部分?如果是这样,为什么结果不是 [("A", "A")....]?谢谢。
    • @BowenLiu * 只是为了方便复制对象。尝试使用标量,然后使用列表。也可以试试print(*zip(*[iter("ABCDEFG")]*2))print(*zip(*[iter("ABCDEFG"), iter("ABCDEFG")]))。然后开始将两者分解成更小的步骤,看看这两个语句中的实际迭代器对象是什么。
    【解决方案3】:

    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)
    

    【讨论】:

    • 温馨提示:当迭代器yields (= returns) 一个项目时,您可以将这个项目想象为“已消费”。所以下次调用迭代器时,它会产生下一个“未使用”的项目。
    【解决方案4】:

    我认为所有答案中遗漏的一件事(对于熟悉迭代器的人来说可能很明显)但对其他人来说并不那么明显 -

    因为我们有相同的迭代器,它被消耗掉,剩下的元素被 zip 使用。所以如果我们只是使用列表而不是迭代器 例如。

    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,你救了我!假设每个人都知道这一点,我无法相信其他答案跳过了这个重要的细节。您能否参考包含此信息的文档?
    【解决方案5】:

    其他很好的答案和 cmets 很好地解释了 argument unpackingzip() 的作用。

    正如Ignacioujukatzel 所说,您将三个对同一个迭代器的引用传递给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
    

    遵循startend的值:

    [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/

    【讨论】:

      【解决方案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
      

      【讨论】:

      【解决方案7】:

      iter(s) 返回 s 的迭代器。

      [iter(s)]*n 为 s 创建一个包含 n 次相同迭代器的列表。

      因此,在执行zip(*[iter(s)]*n) 时,它会按顺序从列表中的所有三个迭代器中提取一个项目。由于所有迭代器都是同一个对象,它只是将列表分组为n 的块。

      【讨论】:

      • 不是“同一个列表的n个迭代器”,而是“同一个迭代器对象的n倍”。不同的迭代器对象不共享状态,即使它们属于同一个列表。
      • 谢谢,已更正。确实这就是我“想”的,但写了别的东西。
      猜你喜欢
      • 2011-01-15
      • 2016-02-16
      • 1970-01-01
      • 2012-03-29
      • 1970-01-01
      • 2019-01-11
      • 1970-01-01
      • 2017-05-25
      • 2014-12-29
      相关资源
      最近更新 更多