当前位置: 首页 > Linux

Python进阶:切片的误区和高级用法

时间:2023-04-06 05:09:36 Linux

2018-12-31更新声明:切片系列文章分三篇写成,现在合并为一篇。合并后修复了一些严重的错误(比如自定义序列切片的部分),在文本结构和章节连接上做了很多改动。原系列的单篇文章就不删了,毕竟也是作为单独的文章。在此声明,请阅读改进版——Python进阶:高级特性切片全面解读!https://mp.weixin.qq.com/s/IR...众所周知,我们可以通过索引值(或下标)来查找序列类型(如string、list、tuple...)单个元素,那如果要获取一个索引范围内的元素怎么办?切片(slice)是一种截取索引碎片的技术。借助切片技术,我们可以非常灵活地处理序列类型的对象。一般来说,切片的作用是截取序列对象。但是,它仍然存在一些误解和高级用法,值得我们注意。因此,本文将主要和大家探讨这些内容,希望大家能够有所收获。事先说明,切片不是列表的排他操作,但由于列表最具代表性,本文仅以列表为例进行讨论。一、切片的基本用法列表是Python中非常基础和重要的数据结构。自己写了一篇总结文章(见文末链接),以便更全面的学习。文章详细总结了切片的基本用法,现复习一下:切片的写法:[i:i+n:m];其中i是切片的起始索引值,当它是列表中的第一个时可以省略;i+n是切片的结束位置,当是链表末尾时可以省略;m可以不提供,默认值为1,不允许为0。当m为负数时,列表将反转。注意:这些值可以大于列表长度,不会报越界。切片的基本含义是:从序列的第i个索引开始,取权到最后n位元素,按m个区间过滤。li=[1,4,5,6,7,9,11,14,16]#下面的写法可以表示整个列表,其中X>=len(li)li[0:X]==li[0:]==li[:X]==li[:]==li[::]==li[-X:X]==li[-X:]li[1:5]==[4,5,6,7]#从1开始,取5-1个元素li[1:5:2]==[4,6]#从1开始,取5-1个元素,过滤li[-1:]==[16]#取倒数第一个元素li[-4:-2]==[9,11]#从倒数第四个取-2-(-4)=2个元素li[:-2]==li[-len(li):-2]==[1,4,5,6,7,9,11]#从头开始??,取-2-(-len(li))=7位元素#当步长为负数时,先翻转列表,然后截取li[::-1]==[16,14,11,9,7,6,5,4,1]#翻转整个listli[::-2]==[16,11,7,5,1]#翻转整个列表,然后按2个区间过滤li[:-5:-1]==[16,14,11,9]#翻转整个列表,取-5-(-len(li))=4个元素li[:-5:-3]==[16,9]#翻转整个列表,取-5-(-len(li))=4个元素,然后按3个区间过滤#切片的步长不能为0li[::0]#报错(ValueError:切片步长不能为零)上面的一些例子是给初学者的(即使对于很多老手来说),可能也不容易理解。我个人总结了两条经验:(1)牢牢记住公式[i:i+n:m],出现默认值时,通过想象完成公式;(2)索引为负,步长为正时,索引位置计算为倒数;当索引为负且步长为负时,先翻转列表,然后计算索引位置作为倒数。2.切片是一个伪独立的对象。切片操作的返回结果是一个新的独立序列(PS:也有例外,见《Python是否支持复制字符串呢?》)。以列表为例,切片后得到的列表还是一个列表,占用了新的内存地址。当切片的结果被取回时,它是一个独立的对象,因此可以用于赋值操作和其他传值场景。但是slice只是浅拷贝,复制的是原list中元素的引用,所以当有变长对象的元素时,新的list会以原list为准。li=[1,2,3,4]ls=li[::]li==ls#Trueid(li)==id(ls)#Falseli.append(li[2:4])#[1,2,3,4,[3,4]]ls.extend(ls[2:4])#[1,2,3,4,3,4]#下面的例子相当于判断li的长度是否大于8if(li[8:]):print("notempty")else:print("empty")#切片列表以原列表为准lo=[1,[1,1],2,3]lp=lo[:2]#[1,[1,1]]lo[1].append(1)#[1,[1,1,1],2,3]lp#[1,[1,1,1]]由于visibility,取出slice结果,可以作为一个独立的对象,但也要注意变量的元素是否-length对象被取出。3.切片可以作为占位符切片可以作为一个独立的对象从原始序列中“取出”,也可以留在原始序列中作为占位符。在写《详解Python拼接字符串的七种方式》的时候,我介绍了几种拼接字符串的方法,其中拼接格式化类的三种方法(即%、format()、template)使用了占位符的思想。对于列表,使用切片作为占位符也可以达到拼接列表的效果。特别需要注意的是,分配给切片的值必须是可迭代对象。li=[1,2,3,4]#在开头连接li[:0]=[0]#[0,1,2,3,4]#在结尾连接li[len(li):]=[5,7]#[0,1,2,3,4,5,7]#中间拼接li[6:6]=[6]#[0,1,2,3,4,5,6,7]#给切片赋值必须是一个可迭代对象li[-1:-1]=6#(error,TypeError:canonlyassignaniterable)li[:0]=(9,)#[9,0,1,2,3,4,5,6,7]li[:0]=范围(3)#[0,1,2,9,0,1,2,3,4,5,6,7]在上面的例子中,如果把切片作为独立对象取出来,你会发现它们都是空列表,即li[:0]==li[len(li):]==li[6:6]==[],我称这种占位符为“纯占位符”。给纯占位符赋值不会破坏原有元素,只会在特定索引位置拼接新元素。删除纯占位符时,列表中的元素也不受影响。对应“纯占位符”,“非纯占位符”的切片是一个非空列表,对它的操作(赋值和删除)会影响到原列表。如果纯占位符可以实现列表的拼接,那么非纯占位符就可以实现列表替换。li=[1,2,3,4]#不同位置的替换li[:3]=[7,8,9]#[7,8,9,4]li[3:]=[5,6,7]#[7,8,9,5,6,7]li[2:4]=['a','b']#[7,8,'a','b',6,7]#非等长替换li[2:4]=[1,2,3,4]#[7,8,1,2,3,4,6,7]li[2:6]=['a']#[7,8,'a',6,7]#删除元素delli[2:3]#[7,8,6,7]切片占位符可以有步长,从而实现连续跨越替换或删除效果。需要注意的是,这种用法只支持等长替换。li=[1,2,3,4,5,6]li[::2]=['a','b','c']#['a',2,'b',4,'c',6]li[::2]=[0]*3#[0,2,0,4,0,6]li[::2]=['w']#错误,尝试分配序列大小为1的扩展切片大小为3delli[::2]#[2,4,6]4.多想想其他编程语言有没有类似Python的切片操作?有什么不同?我在交流群里问过这个问题,小伙伴们讲了Java、Go、Ruby……查了相关资料,发现Go语言的slice是一个很奇葩的设计。首先,它是一种特殊类型,即对数组(array)进行切片后,结果不是数组;其次,可以创建和初始化一个切片,需要声明长度(len)和容量(cap);而且,它还有一个动态的机制,需要扩展到底层数组的边界之外,这有点类似于Python列表的过度分配机制......在我看来,无论是意图,还是写法和使用方式,Python的切片操作都更加清晰易用。因此,本文不再做跨编程语言的比较(好吧,好吧,我承认,我对其他编程语言了解不多。。。)最后还有一个问题:Python的切片操作有什么区别?底层原理呢?我们可以自定义切片操作吗?限于篇幅,我将在下一篇推文中与大家一起学习,敬请期待。延伸阅读:超级总结:学习Python列表,光是这篇文章就足以详细讲解Python拼接字符串的七种方式Python支持复制字符串吗?PS:本公众号(蟒猫)已开通读者交流群,详情请到菜单栏“交流群”查看。--------------本文首发于微信公众号【蟒猫】,后台回复“爱学习”,送你20+精选e-免费书籍。