每个python程序员都知道序列可以用s[a:b]的形式切片,但是关于切片,还有一些不为人所知的一些方面,这里是切片的进阶部分。
切片
在python里,像列表(list),元组(tuple),还有字符串(str)这类序列类型都支持切片操作,但是实际上的切片却比人们想象的要强大的多!
切片的高级用法
为什么切片会忽略最后一个元素
在切片和区间操作里不包含区间范围内的最后一个元素是Python的风格,这个习惯符合Python, C和其他语言里用0作为起始下标的传统。这样带来的好处如下
- 当只有最后一个位置信息时,我们也可以快速的看出切片和区间里有几个元素,rang(3)和my_list[0:3]都返回三个元素
- 当起止位置信息可见时,我们可以快速的计算出切片和区间的长度,用最后一个数减去第一个下标(stop-start)即可
- 这样做也让我们可以利用任意一个下标来把序列分割成不重叠的两部分,只要写成my_list[0:x]和my_list[x:]就可以如下所示:
l = [10,20,30,40,50]
>>> l[:2] # [10,20] 在下标2的地方分割
[10,20]
>>> l[2:]
[30,40,50,60]
>>> l[:3]
[10,20,30]
>>> l[3:]
[40,50,60]
python解释器是如何理解切片的
对对象进行切片
一个众所周知的秘密是,我们还可以用s[a: b:c]的形式对s在a和b之间的以c为间隔取值。c的值还可以为负,负值意外着反向取值。下面的三个例子更直观
# python 切片
>>> s = 'bicycle'
>>>s[::3]
'bye'
>>>s[::-1]
'elcycib'
>>>s[::-2]
'eccb'
还有一个例子是我在另一篇文章中用的的实例python之__getitme__和__len__方法里面已经包含代码的由来,有实例
>>> deck[12::13]
[Card(rank="A",suit="spades"),Card(rank="A",suit="diamonds"),Card(rank="A",suit="clubs"),Card(rank="A",suit="hearts")]
a🅱️c 这种用法只能作为索引或者下标用[]中来返回一个切片对象:slice(a,b,c)之后会单独讲slice方法,对seq[start:stop:step]进行求值的时候,python会调用seq. __getitme __(slice(start,stop,step)).就算你还不会自定义序列类型,了解一下切片对象也是有好处的。例如你可以给切片命名,就像电子表格软件里给单元格区域取名字一样。
从python的用户出发切片还有两个功能: 多维切片,省略表示法
多维切片和省略
[]运算符还可以使用以逗号分开的多个索引或者是切片,外部库NumPy里就用到了这一特效,二维的numpy,ndarray就可以用a[i,j]这种形式来获取,亦或是用a[m:n,k:l]的方式来获得二维切片。下面我们看看例子:
import numpy # 这个不是python的标准库,是一个外部库,需要pip下载安装
a = numpy.arange(12) # 新建一个0~11的整数的numpy.ndarray,然后把他打印出来
print(a) # >>> array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
print(type(a)) # >>> <class 'numpy.ndarray'>
# 看看数组的维度,它是一个一维的,有12个元素的数组
print(a.shape) # >>> (12,)
# 把数组变成二维的,然后打印出来
a.shape = 3, 4
print(a) # >>> array([[ 0, 1, 2, 3],[ 4, 5, 6, 7],[ 8, 9, 10, 11]])
# 打印第二行
print(a[2]) # >>> array([ 8, 9, 10, 11])
# 取出第二行第一个元素
print(a[2, 1]) # >>> 9
# 把第一列打印出来
print(a[:, 1]) # >>> array([1,5,9])
# 把行和列交换,就得到一个新数组
print(a.transpose()) # >>>array([[ 0, 4, 8],[ 1, 5, 9],[ 2, 6, 10],[ 3, 7, 11]])
要正确的处理这种[ ]运算符的话,对象的特殊方法_getitme_和_setitem_需要以元组的形式来接收a[i,j]中的索引。也就是说,如果要得到a[i,j]的值,python会调用a._ getitme_((i,j))。
python内置的序列类型都是一维的,因此他们只支持单一的索引,成对出现的索引是没有用的。
省略(ellipsis)的正确书写方法是三个英语句号(…),而不是Unicdoe码位U+2026表示的半个省略号(…)。省略在python解释器眼里只是一个符号,而实际上它是ellipsis对象的别名,而ellipsis对象又是ellipsis类的单一实例。它可以当做切片规范的一部分,也可以在函数的参数清单中,比如f(a,…,z),或a[i:…]。在NumPy中,…用作多维数组切片的快捷方式,如果X是思维数组,那么x[i:…]就是x[i,:,:,:]的缩写。
给切片赋值
如果把切片放在赋值语句的左边,或把它作为del操作对象,我们就可以对序列进行嫁接,切除或就地修改操作。通关下面这几个例子应该就能体会
l = list(range(10))
print(l) # >>>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
l[2:5] = [20, 30]
print(l) # >>> [0, 1, 20, 30, 5, 6, 7, 8, 9]
del l[5:7]
print(l) # >>> [0, 1, 20, 30, 5, 8, 9]
l[3::2] = [11, 22]
print(l) # >>>[0, 1, 20, 11, 5, 22, 9]
# 如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代对象,即便只有单独的一个值,也要把他转换为可迭代的序列
l[2:5] = 100 # >>> TypeError: can only assign an iterable
l[2:5] = [100]
print(l) # >>> [0, 1, 100, 22, 9]