详解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 相关文章推荐
python基础知识小结之集合
Nov 25 Python
python-opencv在有噪音的情况下提取图像的轮廓实例
Aug 30 Python
用python结合jieba和wordcloud实现词云效果
Sep 05 Python
Python数据类型中的“冒号“[::]——分片与步长操作示例
Jan 24 Python
深入理解Python 关于supper 的 用法和原理
Feb 28 Python
Python生成MD5值的两种方法实例分析
Apr 26 Python
教你如何编写、保存与运行Python程序的方法
Jul 12 Python
详解python中自定义超时异常的几种方法
Jul 29 Python
keras K.function获取某层的输出操作
Jun 29 Python
python对 MySQL 数据库进行增删改查的脚本
Oct 22 Python
python3列表删除大量重复元素remove()方法的问题详解
Jan 04 Python
python 算法题——快乐数的多种解法
May 27 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 中include()与require()的对比
2006/10/09 PHP
关于PHP session 存储方式的详细介绍
2013/06/25 PHP
PHP 获取文件权限函数介绍
2013/07/11 PHP
PHP面向对象程序设计之对象生成方法详解
2016/12/02 PHP
PHP面向对象程序设计之多态性的应用示例
2018/12/19 PHP
不一样的文字闪烁 轮番闪烁
2009/11/11 Javascript
js jquery数组介绍
2012/07/15 Javascript
加载列表时jquery获取ul中第一个li的属性
2014/11/02 Javascript
使用jquery实现仿百度自动补全特效
2015/07/23 Javascript
微信小程序 摇一摇抽奖简单实例实现代码
2017/01/09 Javascript
Angular2 PrimeNG分页模块学习
2017/01/14 Javascript
移动端效果之Swiper详解
2017/10/09 Javascript
基于webpack.config.js 参数详解
2018/03/20 Javascript
webpack4 SCSS提取和懒加载的示例
2018/09/03 Javascript
laydate只显示时分 不显示秒的功能实现方法
2019/09/28 Javascript
vue.js使用v-model实现父子组件间的双向通信示例
2020/02/05 Javascript
vue学习笔记之Vue中css动画原理简单示例
2020/02/29 Javascript
JavaScript常用工具函数库汇总
2020/09/17 Javascript
你不知道的SpringBoot与Vue部署解决方案
2020/11/09 Javascript
实例讲解Python中浮点型的基本内容
2019/02/11 Python
基于Python中isfile函数和isdir函数使用详解
2019/11/29 Python
python GUI库图形界面开发之PyQt5不规则窗口实现与显示GIF动画的详细方法与实例
2020/03/09 Python
python 制作python包,封装成可用模块教程
2020/07/13 Python
CSS3+DIV实现漂亮的动画彩色标签
2016/06/16 HTML / CSS
IE矩阵Matrix滤镜旋转与缩放及如何结合transform
2012/11/29 HTML / CSS
H5最强接口之canvas实现动态图形功能
2019/05/31 HTML / CSS
加拿大品牌鞋包连锁店:Little Burgundy
2021/02/28 全球购物
东方红海科技面试题软件测试方面
2012/02/08 面试题
翻译专业应届生求职信
2013/11/23 职场文书
小学教师听课制度
2014/02/01 职场文书
中国梦口号
2014/06/13 职场文书
十佳标兵事迹材料
2014/08/18 职场文书
2016初一新生军训心得体会
2016/01/11 职场文书
浅谈Python项目的服务器部署
2021/04/25 Python
react中的DOM操作实现
2021/06/30 Javascript
多人盗宝《绿林侠盗》第三赛季4.5上线 跨平台实装
2022/04/03 其他游戏