共计 3016 个字符,预计需要花费 8 分钟才能阅读完成。
家喻户晓,咱们能够通过索引值(或称下标)来查找序列类型(如字符串、列表、元组…)中的单个元素,那么,如果要获取一个索引区间的元素该怎么办呢?
切片(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、切片是伪独立对象
切片操作的返回后果是一个新的独立的序列。以列表为例,列表切片后失去的还是一个列表,占用新的内存地址。
当取出切片的后果时,它是一个独立对象,因而,能够将其用于赋值操作,也能够用于其它传递值的场景。然而,切片只是浅拷贝,它拷贝的是原列表中元素的援用,所以,当存在变长对象的元素时,新列表将受制于原列表。
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、切片可作为占位符
切片既能够作为独立对象被“取出”原序列,也能够留在原序列,作为一种占位符应用。
三种格式化类的拼接办法(即 %、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 的切片操作呢?有什么差别?
我发现 Go 语言的切片是挺奇怪的设计。首先,它是一种非凡类型,即对数组(array)做切片后,失去的居然不是一个数组;其次,你能够创立和初始化一个切片,须要申明长度(len)和容量(cap);再者,它还存在超出底层数组的界线而须要进行扩容的动静机制,这倒是跟 Python 列表的超额分配机制有肯定相似性……
在我看来,无论是用意,还是写法和用法,都是 Python 的切片操作更明了与好用。所以,本文就不再进行跨编程语言的比拟了(唔,好吧我抵赖,其实是我不怎么懂其它编程语言……)
最初,还有一个问题:Python 的切片操作有什么底层原理呢?咱们是否能够自定义切片操作呢?
以上就是本次分享的所有内容,想要理解更多 python 常识欢送返回公众号:Python 编程学习圈,发送“J”即可收费获取,每日干货分享