详解Python进阶之切片的误区与高级用法


Posted in Python onDecember 24, 2018

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

切片(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 # True
id(li) == id(ls) # False
li.append(li[2:4]) # [1, 2, 3, 4, [3, 4]]
ls.extend(ls[2:4]) # [1, 2, 3, 4, 3, 4]

# 下例等价于判断li长度是否大于8
if(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 的切片操作有什么底层原理呢? 我们是否可以自定义切片操作呢?限于篇幅,我将在下次推文中跟大家一起学习,敬请期待。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python2.x中str与unicode相关问题的解决方法
Mar 30 Python
Python环境搭建之OpenCV的步骤方法
Oct 20 Python
深入解析Python小白学习【操作列表】
Mar 23 Python
python+logging+yaml实现日志分割
Jul 22 Python
Python在Matplotlib图中显示中文字体的操作方法
Jul 29 Python
Django Python 获取请求头信息Content-Range的方法
Aug 06 Python
Django对models里的objects的使用详解
Aug 17 Python
Python中sorted()排序与字母大小写的问题
Jan 14 Python
解决pyqt5异常退出无提示信息的问题
Apr 08 Python
Python实现自动签到脚本功能
Aug 20 Python
详解Python调用系统命令的六种方法
Jan 28 Python
Python 绘制多因子柱状图
May 11 Python
Python数据抓取爬虫代理防封IP方法
Dec 23 #Python
python3爬虫怎样构建请求header
Dec 23 #Python
windows下搭建python scrapy爬虫框架步骤
Dec 23 #Python
python构建基础的爬虫教学
Dec 23 #Python
Flask之请求钩子的实现
Dec 23 #Python
python爬虫获取新浪新闻教学
Dec 23 #Python
Python爬虫文件下载图文教程
Dec 23 #Python
You might like
php页面消耗内存过大的处理办法
2013/03/18 PHP
深入Memcache的Session数据的多服务器共享详解
2013/06/13 PHP
ThinkPHP模板引擎之导入资源文件方法详解
2014/06/18 PHP
PHP编程实现微信企业向用户付款的方法示例
2017/07/26 PHP
Laravel学习教程之View模块详解
2017/09/18 PHP
详解php 使用Callable Closure强制指定回调类型
2017/10/26 PHP
PHP实现一个轻量级容器的方法
2019/01/28 PHP
JavaScript与C# Windows应用程序交互方法
2007/06/29 Javascript
传递参数的标准方法(jQuery.ajax)
2008/11/19 Javascript
Javascript 倒计时源代码.(时.分.秒) 详细注释版
2011/05/09 Javascript
js使用for循环与innerHTML获取选中tr下td值
2014/09/26 Javascript
Node.js模块封装及使用方法
2016/03/06 Javascript
jquery easyui datagrid实现增加,修改,删除方法总结
2016/05/25 Javascript
浅谈javascript中的事件冒泡和事件捕获
2016/12/28 Javascript
JavaScript中的遍历详解(多种遍历)
2017/04/07 Javascript
js实现图片局部放大效果详解
2019/03/18 Javascript
[01:22:42]2014 DOTA2华西杯精英邀请赛 5 24 DK VS LGD
2014/05/26 DOTA
深入解析Python中函数的参数与作用域
2016/03/20 Python
Python使用SocketServer模块编写基本服务器程序的教程
2016/07/12 Python
Python 多进程和数据传递的理解
2017/10/09 Python
python实现简单中文词频统计示例
2017/11/08 Python
python检索特定内容的文本文件实例
2018/06/05 Python
python中的数据结构比较
2019/05/13 Python
python 猴子补丁(monkey patch)
2019/06/26 Python
Win10 安装PyCharm2019.1.1(图文教程)
2019/09/29 Python
python对 MySQL 数据库进行增删改查的脚本
2020/10/22 Python
毕业生自我鉴定
2013/12/04 职场文书
教师年度考核自我鉴定
2014/01/19 职场文书
婚前协议书范本
2014/04/15 职场文书
学校党的群众路线教育实践活动总结报告
2014/07/03 职场文书
课堂打架检讨书200字
2014/11/21 职场文书
爱心捐书倡议书
2015/04/27 职场文书
2016年会开场白台词
2015/06/01 职场文书
社区志愿者服务心得体会
2016/01/22 职场文书
Centos环境下Postgresql 安装配置及环境变量配置技巧
2021/05/18 PostgreSQL
Vue Element-ui表单校验规则实现
2021/07/09 Vue.js