Python列表的索引切片还可以这么理解

原文链接:http://www.juzicode.com/python-note-list-slice-index

老规矩先抛问题:

  • 1)、有一个列表lst赋值为lst=[0,1,2,3,4,5,6,7,8,9];
  • 2)、lst[2:-2]的值是多少?
  • 3)、lst[2:]=[21,22,23]和lst[2]=[21,22,23]赋值后lst的值为多少?

下标索引

Python的list是一种有序数据结构,可以像C语言数组那样通过下标方式进行访问,比如定义一个list:lst=[0,1,2,3,4,5,6,7,8,9],它的元素值和下标的对应关系如下图:

这时可以通过指定单个下标的形式例如lst[2]进行访问:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
print("lst[2] =",lst[2])

------运行结果:
lst[2] = 2

除了指定单个下标访问单个元素,还可以用一个冒号分隔起始下标和结束下标的方式访问列表:lst[起始下标 : 结束下标],比如用lst[2:7]表示左闭右开区间[2,7)中2,3,4,5,6的5个下标所表示的元素:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
print("lst[2:7] =",lst[2:7])

------运行结果:
lst[2:7] = [2, 3, 4, 5, 6]

除了用一个冒号分隔起始下标和结束下标,还可以再增加一个冒号后面带步长参数访问列表:lst[起始下标 : 结束下标 : 步长],比如lst[2:7:2]表示从2到7的左闭右开区间中以步长2间隔的元素,包含了下标为2,4,6的几个元素:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
print("lst[2:7:2] =",lst[2:7:2])

------运行结果:
lst[2:7:2] = [2, 4, 6]

负数下标和步长

前面的例子中下标和步长都是正整数,除了用正整数,list还支持用负整数指定下标,这时列表的最后一个元素下标为-1,往左依次减小:

比如lst[-2]表示的从右边起往左第2个元素,注意当使用负数表示时,为了区分从最左侧开始的元素,下标是从-1开始的,而不是像从左开始时下标从0开始:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
print("lst[-2] =",lst[-2])

------运行结果:
lst[-2] = 8

同样地,也可以使用一个冒号分隔起始下标和结束下标,这种方式访问列表仍然满足左闭右开的原则,表示从起始下标到结束下标前一个值所表示的元素;另外需要注意,仍然要满足起始下标小于结束下标的要求,从下面这个例子看到如果起始下标大于结束下标得到的是一个空的list,这点和用正整数时如果起始下标大于结束下标是一样的:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
print("lst[-7:-2] =",lst[-7:-2])
print("lst[-2:-7] =",lst[-2:-7])
print("lst[7:2] =",lst[7:2])

------运行结果:
lst[-7:-2] = [3, 4, 5, 6, 7]
lst[-2:-7] = []
lst[7:2] = []

同样地,我们也可以对步长参数取负数,但是从下面这个例子看来好像并没有得到想要的结果:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
print("lst[-7:-2:-2] =",lst[-7:-2:-2])

------运行结果:
lst[-7:-2:-2] = []

在上面这个例子里下标符号一样,起始下标的值小于结束下标,表示需要从左向右取元素,但是步长为-2则是要从右向左间隔2取元素,二者方向不一致所以得到的是个空的list。在前面提到如果只用一个冒号分隔起始下标和结束下标时,需要保证起始下标小于结束下标,这是因为有一个默认条件在里面,这个默认条件就是当只使用一个冒号时,省略掉的第2个冒号及后面表示的步长参数默认为1,表示向右以步长为1取值。所以由此可以引申开来,当下标符号相同时如果起始下标大于结束下标,则步长参数应该为负数,接下来我们可以验证下:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
print("lst[2:7:-2] =",lst[2:7:-2])
print("lst[7:2:-2] =",lst[7:2:-2])
print("lst[-7:-2:-2] =",lst[-7:-2:-2])
print("lst[-2:-7:-2] =",lst[-2:-7:-2])

------运行结果:
lst[2:7:-2] = []
lst[7:2:-2] = [7, 5, 3]
lst[-7:-2:-2] = []
lst[-2:-7:-2] = [8, 6, 4]

从上面这个例子可以看到起始下标和结束下标以及步长的关系,当下标的符号相同时,如果起始下标小于结束下标,步长应该为正数,反之步长应该为负数,否则得到的是一个空列表

从前面的例子也可以看到当步长参数为负数时,列表中元素的顺序和原来的列表相比反过来了,特别地当步长等于-1时,相当于将原列表倒序排列了

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
print("lst[-1:-10:-1] =",lst[-1:-10:-1])
print("lst[9:0:-1] =",lst[9:0:-1])

------运行结果:
lst[-1:-10:-1] = [9, 8, 7, 6, 5, 4, 3, 2, 1]
lst[9:0:-1] = [9, 8, 7, 6, 5, 4, 3, 2, 1]

这个例子中倒序列表得到的元素仅仅包含9个数值,用什么方法可以包含所有的10个元素?这里先挖个坑,稍后再填。

接下来我们再看下起始下标和结束下标的符号不一样时,“如果起始下标小于结束下标,步长应该为正数,反之步长应该为负数,否则得到的是一个空列表”的结论是否成立呢?下面用一个例子来看下:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
print("lst[-5:3:-1] =",lst[-5:3:-1])
print("lst[-5:3:1] =",lst[-5:3:1])
print("lst[5:-3:-1] =",lst[5:-3:-1])
print("lst[5:-3:1] =",lst[5:-3:1])

------运行结果:
lst[-5:3:-1] = [5, 4]
lst[-5:3:1] = []
lst[5:-3:-1] = []
lst[5:-3:1] = [5, 6]

这个时候前面的结论不成立了,lst[-5:3:1] = [] 这里起始下标-5小于结束下标3的值,步长为正数1,但是得到的却是一个空的列表,lst[-5:3:-1] = [5, 4] 时起始下标-5小于结束下标3步长为负数-1得到的却不是空列表。在符号不同的情况下可以通过将负数符号的下标加列表长度转换为同一种符号后来再套用前面的规则来判断,比如lst[-5:3:-1]等价于lst[-5+10:3] = lst[5:3],而lst[5:-3]等价于lst[5:-3+10] = lst[5:7],下标符号相同后再用前面的规则来判断起始下标和结束下标的大小和步长的关系:

缺省值索引

像上面这样需要知道下标起始和结束值的方式在很多时候使用起来不是很方便,也可以不指明起始下标或结束下标进行访问。比如要访问原始列表中从第2个元素开始到最后一个元素就可以使用lst[2:]的形式,同样地也可以不指明起始元素的下标用lst[:7]表示从最开始(下标为0)到第7个元素(不包含下标7的元素):

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
print("lst[2:] =",lst[2:])
print("lst[2::2] =",lst[2::2])
print("lst[:7] =",lst[:7])
print("lst[:7:2] =",lst[:7:2])

------运行结果:
lst[2:] = [2, 3, 4, 5, 6, 7, 8, 9]
lst[2::2] = [2, 4, 6, 8]
lst[:7] = [0, 1, 2, 3, 4, 5, 6]
lst[:7:2] = [0, 2, 4, 6]

上面关于省略了起始下标或结束下标的例子都是基于步长参数为正整数或不指明步长默认为正整数的情况,下面这个例子步长参数则为负数:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
print("lst[:3:-1] =",lst[:3:-1])
print("lst[7::-1] =",lst[7::-1])

------运行结果:
lst[:3:-1] = [9, 8, 7, 6, 5, 4]
lst[7::-1] = [7, 6, 5, 4, 3, 2, 1, 0]

前面所有的讨论中多次提到左闭右开原则,是指用2个冒号分隔起始下标和结束下标时,第1个冒号后面表示的结束下标表示的元素是不包含在里面的。但是从上面的例子中lst[7::-1]结束下标使用的是缺省值,如果该缺省值理解为0,那么0所表示的元素不应该包含在内,但是lst[7::-1]却包含了下标为0的元素!如果结束下标不使用缺省值,该用什么数值呢才能包含原始列表中下标为0的元素?看下面这个例子,分别用缺省、0、-1和-11(列表长度+1的负值)填入结束下标:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
print("lst[7::-1] =",lst[7::-1])
print("lst[7:0:-1] =",lst[7:0:-1])
print("lst[7:-1:-1] =",lst[7:-1:-1])
print("lst[7:-11:-1] =",lst[7:-11:-1])

------运行结果:
lst[7::-1] = [7, 6, 5, 4, 3, 2, 1, 0]
lst[7:0:-1] = [7, 6, 5, 4, 3, 2, 1]
lst[7:-1:-1] = []
lst[7:-11:-1] = [7, 6, 5, 4, 3, 2, 1, 0]

从运行结果可以看到,如果使用步长为-1的逆序方式访问列表,要想访问到原始列表中下标为0的元素,并不能使用0或-1,而必须用缺省方式或者列表长度+1的负值作为结束下标。回到前面挖坑的地方现在可以填上,要想表示一个完整的倒序列表,可以设置结束下标为缺省方式或者列表长度+1的负值,而步长设为-1:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
print("lst[::-1] =",lst[::-1])
print("lst[-1:-11:-1] =",lst[-1:-11:-1])

------运行结果:
lst[::-1] = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
lst[-1:-11:-1] = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

缺省值到底用什么值来替代呢?从前面这些例子可以得出结论,步长参数拥有高优先级需要最先确定,如果步长参数使用了缺省值则用1表示;如果步长参数缺省或为正数值,起始下标的缺省值为0,结束下标的缺省值为列表长度;如果步长参数为负数值,起始下标的缺省值为-1,结束下标的缺省值为列表长度+1的负数。

修改列表

前面的例子提到的都是获取列表中某个元素或某几个元素,相当于是“读”的过程,同样地也可以用这种方式“写”列表修改列表中元素的值。

比如下面这个例子中用lst[下标n]=x的形式修改单个元素的值,lst[2]=22将下标2的元素的值修改为22:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
lst[2] = 22
print("lst =",lst)

------运行结果:
lst = [0, 1, 22, 3, 4, 5, 6, 7, 8, 9]

同样地,也可以用冒号间隔起始下标和结束下标修改某一个区间的值:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
lst[2:5] = [22,23,24]
print("lst =",lst)

------运行结果:
lst = [0, 1, 22, 23, 24, 5, 6, 7, 8, 9]

在上面这个例子中将从[2,5)的左闭右开区间中2,3,4所在的3个元素修改为22,23,24。在这个例子中修改元素的长度为3,要修改的内容为[22,23,24]长度也为3,二者正好一样,所以正好替代了原来下标2,3,4所表示的值。如果长度不一致会发生什么呢?

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
lst[2:3] = [22,23,24]
print("lst =",lst)

lst = [0,1,2,3,4,5,6,7,8,9]
lst[2:7] = [22,23,24]
print("lst =",lst)

------运行结果:
lst = [0, 1, 22, 23, 24, 3, 4, 5, 6, 7, 8, 9]
lst = [0, 1, 22, 23, 24, 7, 8, 9]

从这里可以看到下标符号相同时,如果结束下标减起始下标的值小于要赋值对象的列表长度,则会在修改的基础上再插入剩余的元素,比如lst[2:3] = [22,23,24]表示要修改下标2的元素并插入23和24。结束下标减起始下标的值大于要赋值对象的列表长度,则会在修改的基础上删除剩余的元素,第2个赋值语句中lst[2:7] = [22,23,24],这时则会将下标2,3,4修改为22,23,24,并将下标5和6的元素删除。

下面这个例子,仅仅一个冒号的差别,就会导致结果千差万别:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
lst[2] = [22,23,24]   #下标中无冒号
print("lst =",lst)

lst = [0,1,2,3,4,5,6,7,8,9]
lst[2:] = [22,23,24]  #下标中有冒号
print("lst =",lst)

------运行结果:
lst = [0, 1, [22, 23, 24], 3, 4, 5, 6, 7, 8, 9] 
lst = [0, 1, 22, 23, 24]

第1次的lst[2] = [22,23,24]是对下标2所在的元素做出修改,将下标2的元素修改为[22,23,24]的list,但是lst[2:] = [22,23,24]则是要对从下标2开始的元素删除并修改为22,23,24。

同样地当增加步长参数赋值:lst[起始下标:结束下标:步长]=[…],则表示按照步长修改元素的值,下面这个例子中lst[2::2] = [22,23,24,25]表示从2开始的下标按照步长2依次修改下标2,4,6,8的值为22,23,24,25,注意这时从起始下标以步长到结束下标遍历的元素个数要等于用来赋值的列表长度相等,否则会抛异常。

lst = [0,1,2,3,4,5,6,7,8,9]
lst[2::2] = [22,23,24,25]
print("lst =",lst)

------运行结果:
lst = [0, 1, 22, 3, 23, 5, 24, 7, 25, 9]

下面是一个长度不等抛异常的例子:

#juzicode.com/vx:桔子code
lst = [0,1,2,3,4,5,6,7,8,9]
lst[2::2] = [22,23,24]
print("lst =",lst)

------运行结果:
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-9-5c459935585a> in <module>
      1 #juzicode.com/vx:桔子code
      2 lst = [0,1,2,3,4,5,6,7,8,9]
----> 3 lst[2::2] = [22,23,24]
      4 print("lst =",lst)

ValueError: attempt to assign sequence of size 3 to extended slice of size 4

  • 小结:
  • 1)list可以通过下标访问,单元素访问的形式为lst[下标],获取或修改的是单个下标所表示的元素。
  • 2)下标还可以用负数表示,最后一个元素的下标为-1,向左边依次减小。
  • 3)用lst[起始下标:结束下标:步长]的方式访问列表时,如果存在缺省值,步长参数拥有高优先级需要最先确定,如果步长参数使用了缺省值则用1表示;如果步长参数为正数值,起始下标的缺省值为0,结束下标的缺省值为列表长度;如果步长参数为负数值,起始下标的缺省值为-1,结束下标的缺省值为列表长度+1的负数
  • 4)如果起始下标和结束下标符号不一致,可以通过在负数值下标上加一次列表长度的方式将符号统一。
  • 5)  下标符号相同时,如果结束下标减起始下标的值小于要赋值对象的列表长度,则会在修改的基础上再插入剩余的元素;结束下标减起始下标的值大于要赋值对象的列表长度,则会在修改的基础上删除剩余的元素。

扩展阅读:

  1. Python基础教程2c–数据类型-list(列表)

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注