python zip的高阶应用

zip() 函数主要用于对可迭代的对象的打包与解包操作。

  • 将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同。
  • 利用 * 号操作符,可以将元组解压为列表。

zip()函数的使用语法如下:

zip(iterable1,iterable2, ...)

其中iterable是- 一个或多个可迭代对象(字符串、列表、元祖、字典等)。
接下来从最基础的用法开始一步步来看一下zip的打包与解包。

1. zip的基本应用

1.1 合并两个列表

>>> list1 = [1,2,3]
>>> list2 = [4,5,6]
>>> zip(list1,list2)
<zip at 0x20cd7ebc708>  

Python3zip返回值与mapfileter类似,是一个类似生成器的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)]   

1.2 将两个列表合成字典

>>> keys = ['width','height','weight']
>>> values = [100,200,50]
>>> dict(zip(keys,values))
    {'width': 100, 'height': 200, 'weight': 50}

zip将参数中的两个列表,按对应位置进行了打包,生成的元组对,经过dict转换为了字典。

1.3 嵌套列表怎么办?

如果是嵌套列表,又该如何使用zip呢?

>>> list3 = [[1,2,3],[4,5,6]]
>>> zip(list3)
    <zip at 0x20cd7ebcd08>
>>> list(zip(list3))
    [([1, 2, 3],), ([4, 5, 6],)]

这里的输出不是我们想要的把两个列表对应位置打包在一起,这该如何是好?请继续看。

2. 使用zip(*)解包

首先,我们要知道,在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)]

2.1 矩阵转置

据此,我们可以很间单的写出矩阵的转置:

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

2.2 矩阵点乘

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

3. zip的高阶应用——合并列表中相邻的n项

我们对如下代码分析:

>>> s = [1,2,3,4,5,6]
>>> n = 3
>>> list(zip(*[iter(s)]*n))
    [(1, 2, 3), (4, 5, 6)]

3.1 补充知识

3.1.1 iter生成迭代器

首先,我们来看python中非常重要的一个内置函数iter()
这里先简要介绍一下关于迭代器的几个概念:(详细内容可参考博客:https://www.cnblogs.com/weiman3389/p/6044963.html

  • 可迭代对象。在python中只要实现了__iter__方法的对象就是可迭代对象,可迭代对象最大的特点就是能使用for循环、列表解析、逻辑操作符这几个操作。
  • 迭代器。具有next方法的对象都是迭代器。在调用next方法时,迭代器会返回它的下一个值。如果next方法被调用,但迭代器没有值可以返回,就会引发一个StopIteration异常。
  • iter()函数。将可迭代对象转换成迭代器。

3.1.2 python中[x]*n实现列表重复n次

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]

3.2 解包分析

我们逐步分析以上代码:
首先,从前面的介绍我们指导,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]
    [<list_iterator at 0x20cd92b5240>,
     <list_iterator at 0x20cd92b5240>,
     <list_iterator at 0x20cd92b5240>]

>>> [it]*3
    [<list_iterator at 0x20cd92b5240>,
     <list_iterator at 0x20cd92b5240>,
     <list_iterator at 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)]

致谢:

  1. Python使用zip合并相邻列表项的方法示例。https://www.codercto.com/a/14709.html
  2. Python可迭代对象、迭代器和生成器。https://www.cnblogs.com/weiman3389/p/6044963.html
  3. Hidden features of Python。 https://stackoverflow.com/questions/101268/hidden-features-of-python
  4. The Python Tutorial 。https://docs.python.org/3/tutorial/controlflow.html#tut-unpacking-arguments
  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值