zip()
函数主要用于对可迭代的对象的打包与解包操作。
zip()
函数的使用语法如下:
zip(iterable1,iterable2, ...)
其中iterable
是- 一个或多个可迭代对象(字符串、列表、元祖、字典等)。
接下来从最基础的用法开始一步步来看一下zip的打包与解包。
>>> list1 = [1,2,3]
>>> list2 = [4,5,6]
>>> zip(list1,list2)
0x20cd7ebc708>
在Python3
中zip
返回值与map
、fileter
类似,是一个类似生成器的zip对象,如果我们要查看其中的值,可以通过for
遍历或者直接用list
来转换成列表。
for
循环遍历:
>>> for i,j in zip(list1,list2):
>>> print(i,j)
1 4
2 5
3 6
>>> for i in zip(list1,list2):
>>> print(i)
(1, 4)
(2, 5)
(3, 6)
list
转成列表:
>>> list(zip(list1,list2))
[(1, 4), (2, 5), (3, 6)]
>>> keys = ['width','height','weight']
>>> values = [100,200,50]
>>> dict(zip(keys,values))
{'width': 100, 'height': 200, 'weight': 50}
zip
将参数中的两个列表,按对应位置进行了打包,生成的元组对,经过dict
转换为了字典。
如果是嵌套列表,又该如何使用zip
呢?
>>> list3 = [[1,2,3],[4,5,6]]
>>> zip(list3)
0x20cd7ebcd08>
>>> list(zip(list3))
[([1, 2, 3],), ([4, 5, 6],)]
这里的输出不是我们想要的把两个列表对应位置打包在一起,这该如何是好?请继续看。
首先,我们要知道,在python
中*
的含义除了乘法外,还表示了对列表或元组的解包。
来,我们看下面的例子:
>>> def print_x_y(x,y):
>>> print('x:',x,'y:',y)
>>> xy = [1,2]
>>> print_x_y(1,2)
x: 1 y: 2
>>> print_x_y(*xy)
x: 1 y: 2
>>> print_x_y(xy)
TypeError: print_x_y() missing 1 required positional argument: 'y'
函数print_x_y
的参数为两个,如果我们直接把xy
这个列表输入的话会因为就输入了一个值而报错误,如果我们输入参数为*xy
时就得到了正确的结果,因而,*
的含义就是将列表或元组中的多个元素给解包出来。
我们继续上面的问题,我们可以把zip([[w,x],[y,z]])
中的两个内层列表[w,x]
、[y,z]
看成两个参数,对嵌套列表list3
解包后,对嵌套的每个内层列表进行打包:
>>> list(zip(*list3))
[(1, 4), (2, 5), (3, 6)]
据此,我们可以很间单的写出矩阵的转置:
>>> matrix = [ [1,2,3,4],
[5,1,2,3],
[9,5,1,2],
[4,9,5,1],
[7,4,9,5]]
>>> list(map(list,list(zip(*matrix))))
[[1, 5, 9, 4, 7],
[2, 1, 5, 9, 4],
[3, 2, 1, 5, 9],
[4, 3, 2, 1, 5]]
>>> matrix1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> matrix2 = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
# 矩阵点乘
>>> print([[x1*x2 for x1, x2 in zip(row1, row2) ] for row1, row2 in zip(matrix1, matrix2) ])
[[1, 2, 3], [8, 10, 12], [21, 24, 27]]
# 矩阵相加
>>> print([[x1+x2 for x1, x2 in zip(row1, row2) ] for row1, row2 in zip(matrix1, matrix2) ])
[[2, 3, 4], [6, 7, 8], [10, 11, 12]]
我们对如下代码分析:
>>> s = [1,2,3,4,5,6]
>>> n = 3
>>> list(zip(*[iter(s)]*n))
[(1, 2, 3), (4, 5, 6)]
首先,我们来看python中非常重要的一个内置函数iter()
。
这里先简要介绍一下关于迭代器的几个概念:(详细内容可参考博客:https://www.cnblogs.com/weiman3389/p/6044963.html)
__iter__
方法的对象就是可迭代对象,可迭代对象最大的特点就是能使用for循环、列表解析、逻辑操作符这几个操作。 next
方法的对象都是迭代器。在调用next
方法时,迭代器会返回它的下一个值。如果next
方法被调用,但迭代器没有值可以返回,就会引发一个StopIteration
异常。iter()
函数。将可迭代对象转换成迭代器。 python中可通过[x]*n
将列表[x]
中的元素重复n次变成 [x,x,...]
,
>>> lst1 = ['a'] * 3
>>> lst1
['a', 'a', 'a']
>>> lst1[0] = 'b'
['b', 'a', 'a']
不过当我们执行下述代码时,看看发生了什么?
>>> lst2 = [[1,2]] * 3
>>> lst2
[[1,2], [1,2], [1,2]]
>>> lst2[0][0] = 0
>>>> lst2
[[0, 2], [0, 2], [0, 2]]
WTF?发生了什么?这是因为[x] * 3这个操作实际上没有复制x,而只是创建了三个object reference,当x为不可变对象时,改变其中的某个值另外的值不会变,而当x为可变对象时(列表),改变lst2[0][0]
的同时也改变了lst2[1][0]
和lst2[2][0]
。
我们逐步分析以上代码:
首先,从前面的介绍我们指导,zip
可以将多个列表打包。我们可以很容易理解以下代码:
>>> list(zip(s,s,s))
[(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5), (6, 6, 6)]
>>> list(zip(*[s,s,s]))#将列表中参数解包给函数,所以与上面完全相同
[(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5), (6, 6, 6)]
python中的list
为可迭代对象但不是迭代器,经过iter()
转换后变成了迭代器,这个转换过程发生了什么变化呢?
list
中做遍历的时候,遍历完之后list
中的元素不发生变化,而变成了迭代器以后,对迭代器遍历每次执行的都是next
函数,迭代器中前面的元素就不存在了,只有下一个元素。
如图所示,上图为列表,下图为迭代器。
上图中蓝色数字为遍历的顺序,对s按次序遍历3次,右侧是zip(s,s,s)
的结果,而下图中,因为3个it
是同一个迭代器的引用,每次遍历都只有next
,故遍历6次生成了两个元组。
>>> it = iter(s)
>>> [it,it,it]
[0x20cd92b5240>,
0x20cd92b5240>,
0x20cd92b5240>]
>>> [it]*3
[0x20cd92b5240>,
0x20cd92b5240>,
0x20cd92b5240>]
>>> list(zip(it,it,it))
[(1, 2, 3), (4, 5, 6)]
回到我们最初的代码,zip(*[it]*3)
即zip(*[it,it,it])
即zip(it,it,it)
,因此:
list(zip(*[it]*3))
[(1, 2, 3), (4, 5, 6)]
一定要注意,这里的三个it
是同一个迭代器,如果传入的三个参数是分别取迭代器的话,那么和传入三个list是一样的效果了,大家可以对比上图思考一下:
>>> list(zip(iter(s),iter(s),iter(s)))
[(1, 1, 1),
(2, 2, 2),
(3, 3, 3),
(4, 4, 4),
(5, 5, 5),
(6, 6, 6),
(7, 7, 7),
(8, 8, 8),
(9, 9, 9)]