1 minute read

zip 这个函数,用起来总是没有什么信心……我们上一下 The Python Standard Library 上的示例代码看看(注意:这并非源码,因为现在已经是返回一个 zip 对象了):

def zip(*iterables):
    # zip('ABCD', 'xy') --> Ax By
    sentinel = object()
    iterators = [iter(it) for it in iterables]
    while iterators:
        result = []
        for it in iterators:
            elem = next(it, sentinel)
            if elem is sentinel:
                return
            result.append(elem)
        yield tuple(result)

注意 iterators 是个 list,所以 len(iterators) != 0 时,while iterators 就是个死循环,得等到里面的 return 执行才能退出。

那么第一个问题来了:什么时候 iterators 为空?

因为 iterables 是个 tuple(注意 parameters 传进来以后是被包装到 iterables 这个 tuple 里的,我并不是说要传一个 tuple 进来),所以即使你只传一个参数进来, iterables 非空,所以 iterators 也非空。只有不传参数时,iterators 才为空,此时 zip() 是个 empty generator。

题外话:没有 return 的 function 实际是默认 return None 的,比如:

def func():
    pass
    
a = func()  # OK! Unbelievable!

print(a is None)  # True
print(func() is None)  # True

按理说,如果 zip() 是个 empty generator,那么它也应该返回 None 才对,但是:

list(zip())     # OK. ==[]
list(*zip())    # OK. ==[]
list(None)      # TypeError: 'NoneType' object is not iterable
list(*None)     # TypeError: type object argument after * must be an iterable, not NoneType

(暂时不要问我 list(zip())list(*zip()) 有什么区别……)所以 zip() 并没有返回 None,empty generator 的逻辑肯定是在 zip 对象里单独处理了。

参数场景一:zip([1,2], [3,4])

一点点分析:

  • 首先 iterables == ([1,2], [3,4])
  • iterators == [iter([1,2]), iter([3,4])]
  • while 循环第一次:
    • for 循环第一次:
      • result == [1]
    • for 循环第二次:
      • result == [1,3]
    • yield (1, 3)
  • while 循环第二次:
    • for 循环第一次:
      • result == [2]
    • for 循环第二次:
      • result == [2,4]
    • yield (2, 4)
  • while 循环第三次:
    • for 循环第一次:
      • return
  • 结束

所以 zip([1,2], [3,4]) generates (1,3) and (2,4):

list(zip([1,2], [3,4]))  # OK. ==[(1, 3), (2, 4)]

场景一特有技术一:并行 iterate 两个 list

a_list = [1,2]
b_list = [3,4]

for a, b in zip(a_list, b_list):
    print(a,b)
    
# output
#   1 3
#   2 4

场景一特有技术二:一个 listcomp 返回两个 list

square_cube = [[i**2, i**3] for i in range(5)]
square, cube = zip(*square_cube)

print(square)   # (0, 1, 4, 9, 16)
print(cube)     # (0, 1, 8, 27, 64)

这个技术可以看做是 “并行 iterate” 的变种。当然,这个技术我觉得有点二,还不如写两个 listcomp……

参数场景二:zip([1,2,3,4])

一点点分析:

  • 首先 iterables == ([1,2,3,4],)
  • iterators == [iter([1,2,3,4])]
  • while 循环第一次:
    • for 循环第一次:
      • result == [1]
    • yield (1,)
  • while 循环第二次:
    • for 循环第一次:
      • result == [2]
    • yield (2,)
  • ……
  • while 循环第五次:
    • for 循环第一次:
      • return
  • 结束

所以 zip([1,2,3,4]) generates (1,), (2,), (3,) and (4,):

list(zip([1,2,3,4]))  # OK. ==[(1,), (2,), (3,), (4,)]

我们可以看到,如果只给 zip 传一个参数——假定这个参数名是 sth_iterable——zip 其实就是把 sth_iterable iterate 了一遍,而且是一边 iterate 一边 yield,所以 zip(sth_iterable) 相当于 get_generator_of(sth_iterable)

Intuitive: 切菜

a, b, c 想象成 3 根芹菜(whatever,长条形状的就行),zip(a, b, c) 相当于把这 3 根芹菜对齐,每次都是左手在芹菜上找一个单位长度,比方说 1 厘米,(这相当于在 iterate),然后右手拿刀一次切下三块(这相当于在 yield)。

这么想的话,场景二也就好理解了——你只有一根芹菜。

能切多少刀,取决于最短的芹菜的长度。

Categories:

Updated:

Comments