Python中的zip(), *zip()与zip(*zip(a,b))

作为一个Python初学者,昨天在实践书上一个实例时遇到了zip()和*zip()函数,而书中恰好对这两个函数没有过多的解释(这两个函数其实是同一个函数),网上搜索后对zip()倒是弄明白了,但是对*zip()的说法,CSDN上大部分人却是说其是zip()的逆操作云云,但是操作下来感觉完全不是一回事,后来又搜了几个不同的文章,结合自己的代码操作后才完全把这个事情弄明白,所以将其记录下来,方便自己的同时也看看能不能帮助到同样有疑惑的Python初学者们。


首先说结论:

1. zip()和*zip()是同一个函数,而非后者是前者的逆操作,两者操作结果不同的原因主要是因为星号*的存在
2. 通俗的说,倒不如说zip()是压缩操作,而*星号是解压缩操作,* 才是zip()的逆操作

下面简述zip()函数并用coding来说明我的结论:

Zip():
作用:压缩数组,把多个列表压缩成多个多元元素(ps.这里的几元元素指的是一个元素里有几个值,如果我的说法不准确或者错用了名词,欢迎大家指正我)
特性:
1. 当操作数之间长度不一致时,其长度与最短操作数的长度一致
a = [1,2,3] 
b = [4,5,6,7] 
c = [4,5,6,7,8]  
zipped = zip(a,b,c) 

print(*zipped) = (1, 4, 4) (2, 5, 5) (3, 6, 6)
a, b, c的长度分别为3,4,5,最后zipped的长度为3,把三个列表压缩成了3个三元元素,(为什么这里输出时要加个*号呢,请大家往下看)
2. 在python3中,zip()函数返回的值是一个迭代器,这个迭代器表示的是数据存储在内存中的哪个位置,而不能直接进行访问/输出/使用,需要用list,for循环或者*号解压后才能正常显示。
print(zipped)
print(list(zipped))
print(*list(zipped))
#输出分别为
<zip object at 0x014C22E8>
[(1, 4, 4), (2, 5, 5), (3, 6, 6)] 
(1, 4, 4) (2, 5, 5) (3, 6, 6)
此时我们分别对比输出结果,反应快的朋友可能已经发现了,*号的存在使得输出结果“少了一层”,从一个由“三个元组组成的列表”变成了“三个元组”(ps.元组就是组内元素不可改变的列表)。
所以也可以说,*号让一个二维列表变成了多个一维元组。所以当我简单的print(*zipped)时,星号的作用是把zipped的“迭代器”格式解了一层,把它变成了可以打印出来的“数据格式”,接下来我会在下面用coding再次简述一下*号。

注意:有时候你可能会发现,你输出完zipped后,想再次输出时,列表里为空了。这是因为,zip()在python3中返回一个迭代器,你只能在迭代器迭代一次,用完后元素虽然不会消失,但再次迭代会得到0个元素,除zip外,map,filter也是如此,因为python就是这么规定的。


*号的特性:对操作数进行“解压“
a = 5
b = [3,4,5]
c = [(3,4,5), (6,7,8)]
d = [[(3,4,5),(6,7,8)]]
---
print(*a)  #结果会出现error
print(*b)
print(*c)
print(*d)
#输出分别为
Error
3 4 5
(3, 4, 5) (6, 7, 8)
[(3, 4, 5), (6, 7, 8)]
可以发现,*号把所有操作数都解压了一层,二维列表变成了一维,三维变成了二维,而对int进行解压报错时,错误提示为

print() argument after * must be an iterable, not int
*号后必须为迭代器,而不能是int类型的值

可以简单理解为int已经是“数据基础格式”了,不能再进行解压缩了

简述完zip()和*号,接下来就是最后的zip(*zip(a,b))及利用zip进行矩阵运算。
很多人说*zip()是zip()的逆操作,但是当他们展示code时,却往往写的是zip(*zip(a,b))(CSDN上超过一半的人对zip(), *zip()都是这样说的,直至我搜关于*星号的文章后才恍然大悟)按照这个逻辑,其实应该是*zip(zip(a,b))才对,因为根据函数嵌套,总是先运行括号内的,zip(*zip(a,b))不就变成“先解压缩再进行压缩”了?
然而,*zip(zip(a,b))后,输出的结果并不是预想中的结果,反而m,n = zip(*zip(a,b))输出的结果才是原来的结果,这是为什么呢?
1. *zip(zip(a,b))
首先说一下,当zip后只有一个列表时,他会进行迭代操作,把这个列表和元素为0的空列表进行迭代,所以当zip的操作数只有一个时,程序并不会报错。
a = [1,2,3]
zipped = zip(a) 
print(list(zipped)) = [(1,), (2,), (3,)]
a = [1,2,3]
b = [4,5,6,7]
zipped = zip(a,b)     #此时zipped中数据为(1,4) (2,5) (3,6)
zippeded = zip(zipped)  #再调用一次zip
print(*zippeded) = ((1, 4),) ((2, 5),) ((3, 6),)    #简直和原来的a,b大相径庭
2. m,n = zip(*zip(a,b))
m = [1, 2, 3]
n = [4, 5, 6]

print("zip(m, n)返回:", list(zip(m, n)))
print("*zip(m, n)返回:", *zip(m, n))
m2, n2 = zip(*zip(m, n))
print("m2和n2的值分别为:", m2, n2)
#输出结果为
zip(m, n)返回: [(1, 4), (2, 5), (3, 6)]
*zip(m, n)返回: (1, 4) (2, 5) (3, 6)
m2和n2的值分别为: (1, 2, 3) (4, 5, 6)
虽然括号不一样了,从一开始的[ ]变成了后面的( ),但是既然元组是元素不可变的列表,两者的值其实都是一样的,进一步拆解一下:
#因为*zip(m,n)返回值为(1,4) (2,5) (3,6),此时再对其zip一遍,是不是等效于下面的代码了
_1 = (1,4)
_2 = (2,5)
_3 = (3,6)
m1,m2 = zip(_1,_2,_3)    #分别把结果的2个元素赋给m1和m2,所以又还原成了(1,2,3) (4,5,6)
所以,并不是*zip()是逆操作,而是又进行了一次zip()操作,把列表转置回来了。
因此,zip()也可用于进行矩阵运算,当你进行一次zip时,横坐标和纵坐标调换了一次,这在矩阵乘法中很常见,而使用2次zip操作,则又把横纵坐标调换回来了,或许这也是各位CSDN作者只从结果看,又看到zip()和*zip()这两个看起来如此对仗的操作,便把两者当成互逆操作的原因吧,其实*在其中的作用只是解压了第一次zip后的迭代器格式,把其转换成数据格式罢了。
带*号变量严格来说并不是一个变量,在解压后,它其实是一个参数,一个数据,它不能作为一个变量而被赋值,但可以作为参数传递。正如你不能对int数1,2,3,4,5赋值一样

洋洋洒洒写了不少,希望能帮助到大家,如果我的文章中有什么错误或者不准确的地方,也欢迎大家勘误,大家共同学习。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值