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

众所周知,我们可以通过索引值(或称下标)来查找序列类型(如字符串、列表、元组…)中的单个元素,那么,如果要获取一个索引区间的元素该怎么办呢?

切片(slice)就是一种截取索引片段的技术,借助切片技术,我们可以十分灵活地处理序列类型的对象。通常来说,切片的作用就是截取序列对象,然而,它还有一些使用误区与高级用法,都值得我们注意。所以,本文将主要跟大家一起来探讨这些内容,希望你能学有所获。

事先声明,切片并非列表的专属操作,但因为列表最具代表性,所以本文仅以列表为例作探讨。

1、切片的基础用法

列表是 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位元素,按2间隔过滤
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] # 翻转整个列表
li[::-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间隔过滤

# 切片的步长不可以为0
li[::0]  # 报错(ValueError: slice step cannot be zero)

上述的某些例子对于初学者(甚至很多老手)来说,可能还不好理解。我个人总结出两条经验:

(1)牢牢记住公式[i : i+n : m] ,当出现缺省值时,通过想象把公式补全;

(2)索引为负且步长为正时,按倒数计算索引位置;索引为负且步长为负时,先翻转列表,再按倒数计算索引位置。

2、切片是伪独立对象

切片操作的返回结果是一个新的独立的序列(PS:也有例外,参见《Python是否支持复制字符串呢?》)。以列表为例,列表切片后得到的还是一个列表,占用新的内存地址。

当取出切片的结果时,它是一个独立对象,因此,可以将其用于赋值操作,也可以用于其它传递值的场景。但是,切片只是浅拷贝,它拷贝的是原列表中元素的引用,所以,当存在变长对象的元素时,新列表将受制于原列表。

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('not empty')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]]

由于可见,将切片结果取出,它可以作为独立对象使用,但是也要注意,是否取出了变长对象的元素。

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 # (报错,TypeError: can only assign an iterable)
li[:0] = (9,) #  [9, 0, 1, 2, 3, 4, 5, 6, 7]
li[:0] = range(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]

# 删除元素del li[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'] # 报错,attempt to assign sequence of size 1 to extended slice of size 3

del li[::2] # [2, 4, 6]

4、更多思考

其它编程语言是否有类似于 Python 的切片操作呢?有什么差异?

我在交流群里问了这个问题,小伙伴们纷纷说 Java、Go、Ruby……在查看相关资料的时候,我发现 Go 语言的切片是挺奇怪的设计。首先,它是一种特殊类型,即对数组(array)做切片后,得到的竟然不是一个数组;其次,你可以创建和初始化一个切片,需要声明长度(len)和容量(cap);再者,它还存在超出底层数组的界限而需要进行扩容的动态机制,这倒是跟 Python 列表的超额分配机制有一定相似性……

在我看来,无论是用意,还是写法和用法,都是 Python 的切片操作更明了与好用。所以,本文就不再进行跨编程语言的比较了(唔,好吧我承认,其实是我不怎么懂其它编程语言……)

最后,还有一个问题:Python 的切片操作有什么底层原理呢? 我们是否可以自定义切片操作呢?限于篇幅,我将在下次推文中跟大家一起学习,敬请期待。

(0)

相关推荐

  • 第一次把 Python 的切片理解得如此透彻

    来源:Python猫 作者:豌豆花下猫 众所周知,我们可以通过索引值(或称下标)来查找序列类型(如字符串.列表.元组-)中的单个元素,那么,如果要获取一个索引区间的元素该怎么办呢? 切片(slice) ...

  • RealPython 基础教程:Python 中的列表和元组

    列表(list)和元组(tuple)几乎可称得上是 Python 中最常用.最有用的数据类型了.在每个非简单的 Python 中,你都能发现它们的使用之处. 本文将介绍 list 和 tuple 的重 ...

  • Python列表与元组有什么作用?入门分享!

    Python数据类型分为七大类,其中最为常见的就是列表和字典,是使用Python必须掌握的基础.那么Python列表和字典有什么不同之处?我们一起来看看吧. 列表 1. 任意对象的有序集合,列表是一组 ...

  • 基础语法第4关笔记

      主线课程: 一.列表   1.1 列表的概念 列表是Python中有序可变的一种数据类型,在编程里,列表中的数据被称为列表的元素.列表的组成如下图:   列表是一个包容的数据类型. 其 ...

  • Go 数据结构和算法篇(十):二分查找的变形版本

    Go语言中文网 今天 以下文章来源于xueyuanjun ,作者xueyuanjun 日常开发过程中,除了我们上篇讲到的正常的二分查找,还有很多二分查找的变形版本,今天开始,我们就来给大家一一介绍这些 ...

  • Python学习手册(第4版).3

    建议:如果想要在IDLE的主窗口中重复前一条命令,可以使用Alt-P组合键回滚,找到命令行的历史记录,并用Alt-N向前寻找(在Mac上,可以试试使用Ctrl-P和Ctrl-N).之前的命令可以重新调 ...

  • 第 61 天:Python Requests 库高级用法

    上一篇我们介绍了 Requests 库的基本用法,学会之后大家就可以应付一般的请求了.这一篇我们接着介绍 Requests 的高级用法,以便应付一些棘手的问题. 会话维持 在 requests 中,直 ...

  • Python的 5 种高级用法,效率提升没毛病!

    任何编程语言的高级特征通常都是通过大量的使用经验才发现的.比如你在编写一个复杂的项目,并在 stackoverflow 上寻找某个问题的答案.然后你突然发现了一个非常优雅的解决方案,它使用了你从不知道 ...

  • Python教程:print()函数高级用法

    前面使用print()函数时,都只输出了一个变量,但实际上print()函数完全可以同时输出多个变量,而且它具有更多丰富的功能. print()函数的详细语法格式如下: print (value,.. ...

  • Python高级用法总结—(列表推导式,迭代器,生成器,装饰器)

    Python高级用法总结-(列表推导式,迭代器,生成器,装饰器) 列表推导式(list comprehensions) 场景1:将一个三维列表中所有一维数据为a的元素合并,组成新的二维列表. 最简单的 ...

  • Python的五种高级用法是什么?

    学习Python的时候,掌握这五种高级用法,可以让你的效率提升数十倍,那么你知道Python的五种高级用法是什么吗?快来看看吧. 第一种:Lambda函数 Python函数一般使用def a_func ...

  • 玉米饵不好用?这3个高级用法,学会连买小药钱都省了

    一.嫩玉米打碎后制作搓饵 将嫩玉米粒粉碎成渣(注意是渣不是浆,湖库大鱼喜欢略粗糙的食物),用菜刀剁,用石头砸都可以.把嫩玉米渣连渣带水一起放入容器中上锅蒸熟,不用添加任何东西,要的就是玉米自然的甜香味 ...

  • MACD高级用法之一——稳健买入法+2点卖出法

    MACD高级用法之一--稳健买入法+2点卖出法 出处:本站整理时间:2013-05-16 09:16:00人气:146404 Tags: 通过近期和朋友们教室里交流,很多朋友针对于A股市场上的买卖点把 ...

  • MACD高级用法三大战法之二——波段买卖法

    MACD高级用法三大战法之二--波段买卖法 出处:本站整理时间:2013-05-16 09:38:00人气:102012 Tags: 波段一直以来是朋友们在股市中操作的难题,行情想做短线,不喜欢做中线 ...

  • MACD高级用法三大战术之三——神出鬼没法

    MACD高级用法三大战术之三--神出鬼没法 出处:本站整理时间:2013-05-16 09:35:00人气:66065 Tags: MACD前两个战法在教室里受到了广大朋友们的赞好,在这里我也非常感谢 ...