Python --- 切片详解 (正负索引、索引越界、返回浅复制)( 底层解析)

一、Python可切片对象的索引方式

列表元素支持用索引访问,正向索引从0开始

colors=["red","blue","green"]

colors[0] =="red"

colors[1]=="blue"

同时,也可以使用负向索引(python中有序序列都支持负向索引)

colors[-1]=="green"

以列表 lst = [ 'a', 'b', 'c', 'd', 'e' ] 为例

在这里插入图片描述

二、切片操作

[ start_index : end_index : step ]

切片适用于列表、元组、字符串、range对象等类型。由于集合、字典等无序,无法实现切片。

切片的返回结果类型和切片对象类型一致,返回的是切片对象的子序列,如:对一个列表切片返回一个列表,字符串切片返回字符串。

切片生成的子序列元素是源版的拷贝。因此切片是一种浅拷贝。

1、参数:

  • start_index:表示开始索引。正索引默认为0,负索引默认为 -1。
  • end_index:表示结束索引。正索引默认为列表长度 len(lst),负索引默认为 -len(lst)。
  • step:切片的步长。默认为1。正数表示从左往右切,start_index、end_index采用正索引默认值;负数表示从右往左切,start_index、end_index采用负索引默认值。(步长不能为0,否则抛出ValueError异常)

**【注意:】**切片的开始总是被包括在结果中,而结束不被包括。
即:
[0:len(lst):1]     —— 开始索引为 0,结束索引为  len(lst) ,而切到的最后一个元素的索引为   len(lst)-1  ;
[-1:-len(lst):-1] —— 开始索引为-1,结束索引为 -len(lst) ,而切到的最后一个元素的索引为 -(len(lst)-1) 。

因此,切片时对象的索引可以看做下图形式:

在这里插入图片描述

2、参数运用的几个要点:

  • 总是从 start_index 开始切取元素。

  • 正索引与负索引可以混用,且 start_index 和 end_index 以及 step 不受当前列表对象的实际索引长度限制,可以认为取值是(-∞,+∞)。(切片中的越界索引会被自动处理。详见后文)

  • start到end的区间方向和step方向相反,则返回空列表 []

    #例如:
    >>>lst[1:4:-1]
    []		#返回空列表
    
  • start == end 时,返回空列表[]

    #例如:
    >>>lst[1:1:]
    []		#返回空列表
    >>>lst[-4:-4:]
    []		#返回空列表
    

三、关于负索引

注意:正式句法规则并没有在序列中设置负标号的特殊保留条款

       但是,切片序列是通过__getitem__() 方法解析索引的,内置序列所提供的 getitem() 方法可通过在索引中添加序列长度来解析负标号 (这样 x[-1] 会选出 x 中的最后一项)。 结果值必须为一个小于序列中项数的非负整数,抽取操作会选出标号为该值的项(从零开始数)。 由于对负标号和切片的支持存在于对象的 getitem() 方法,重载此方法的子类需要显式地添加这种支持。

object.__getitem__(self, key)

调用此方法以实现 self[key] 的求值。对于序列类型,接受的键应为整数和切片对象。
请注意负数索引(如果类想要模拟序列类型)的特殊解读是取决于 __getitem__() 方法。
如果 key 的类型不正确则会引发 TypeError 异常;
如果为序列索引集范围以外的值(在进行任何负数索引的特殊解读之后)则应引发 IndexError 异常。
对于映射类型,如果 key 找不到(不在容器中)则应引发 KeyError 异常。

注解:for 循环在有不合法索引时会期待捕获 IndexError 以便正确地检测到序列的结束。

注意:

>>>lst[-5:]		#此时,endindex默认为5,step默认为1
[a,b,c,d,e]

>>>lst[-5:-1]
[a,b,c,d]

>>>lst[-5:5]	#等价于lst[-5:]
[a,b,c,d,e]

四、切片索引越界的处理

切片中的越界索引会被自动处理。

切片用于创建新列表。如果索引区间不在列表中元素数量的范围内,我们可以返回一个空列表。所以,我们不必抛出错误。

但是,如果我们直接访问列表中大于元素数量的元素,我们就不能返回任何默认值(甚至不是None,因为它可能是列表中的有效值)。这就是为什么IndexError: list index out of range在切片中被扔了。

可选参数 start 和 end 是切片符号,用于将搜索限制为列表的特定子序列。返回的索引是相对于整个序列的开始计算的,而不是 start 参数。(切片索引会被静默截短到允许的范围;如果指定索引不是整数则 TypeError 会被引发。)

切片时,如果切片区间不在该对象的实际索引范围类,则将返回序列的长度设置为0,this line中

defstop = *step < 0 ? -1 : length;
...
if (r->stop == Py_None) {
    *stop = defstop;
}
...
if ((*step < 0 && *stop >= *start)
    || (*step > 0 && *start >= *stop)) {
    *slicelength = 0;

对于字符串,如果切片后返回的字符串长度为0,则返回空字符串,this line中

if (slicelength <= 0) {
    return PyString_FromStringAndSize("", 0);
}

其他可切片对象参照以上代码,这里不再引述。

五、切片返回潜复制

       切片生成一个新的序列,并且把原序列中所有元素的引用都复制到新序列中。这意味着以下切片操作会返回列表的一个 潜复制 。

Python 中赋值语句不复制对象,而是在目标和对象之间创建绑定 (bindings) 关系。对于自身可变或者包含可变项的集合对象,开发者有时会需要生成其 副本 用于改变操作,进而避免改变原对象。

Ⅰ、浅复制和深复制的区别:

1.浅复制

浅复制构造一个新的复合对象,然后(尽可能地)插入原始对象包含的相同对象。
( 相当于原aList[0]与切片的bList[0]作为 同一个元素值的引用 共同指向第一个元素1的地址空间。)

(1)潜复制不会引发错误的情况:浅复制列表中只包含可哈希对象时(即地指定位值唯一的对象。如值类型、字符串、元组等。)

程序如下,虽然aList[0]aList[0]共同指向第一个元素1的地址空间,但是由于1是值类型是不可变对象,因此改变aList[0] 的元素值时,不能通过 aList[0] 指针改变该地址空间的 1,只能通过指向其他地址空间来实现元素值的改变。而bList为潜复制,bList[0]1 的引用(指针),bList[0]任然指向 1 的地址空间。此时潜复制没有表现出危害性。

>>>aList[1,2,3]
>>>bList = alist[:]

>>>aList[1] = 0
>>>aList
[1,0,3]
>>>bList
[1,2,3]			#修改aList不影响bList
>>>aList[1,2,3]
>>>bList = aList[:]

>>>bList[0] = 0
>>>aList
[1,2,3]			#修改bList也不影响aList
>>>bList
[0,2,3]

(2)潜复制会引发错误的情况:浅复制列表中包含不可哈希对象时(即可改变的对象,引用对象。如列表、字典等。)

程序如下,此时,可变对象[2]为引用类型,

  • [2]原地操作时,aList[1]bList[1] 始终指向对象[2],因此原地操作修改[2][2,5]时,aList[1]bList[1]都影响。
  • 语句aList[1] = [2,5][2]非原地操作时,根据python的内存管理[2][2,5]地址空间不同,为两个不同对象,aList[1]指向新元素[2,5]bList[1]保持原状指向[2]

无名可变对象:

>>>aList[1,[2],3]		#元素[2]为无名可变对象,临时可变对象
>>>bList = aList[:]

>>>aList[1].append(5)	#原地操作。注意:若不原地操作,修改前后不是同一对象。
>>>aList
[1, [2, 5], 3]
>>>bList
[1, [2, 5], 3]	#修改aList的可变对象元素时,会影响bList。反之亦然。(修改不可变对象时依旧互不影响)

有名可变对象:

>>>lst = [2]
>>>aList[1,lst,3]		#元素lst为可变对象
>>>bList = aList[:]

>>>lst.append(5)	#原地操作。注意:若不原地操作,修改前后不是同一对象。
>>>aList
[1, [2, 5], 3]
>>>bList
[1, [2, 5], 3]		#修改可变对象元素时,会同时影响aList和bList。(修改不可变对象时依旧互不影响)

可变对象非原地操作时:

>>>aList[1,[2],3]		#元素[2]为无名可变对象,临时可变对象
>>>bList = aList[:]

>>>aList[1] = [2,5]		#非原地操作,修改前后不是同一对象。即[2][2,5]地址空间不同。
>>>aList
[1, [2, 5], 3]
>>>bList
[1, [2], 3]		#非原地操作,改变aList的可变对象元素,不会影响bList。反之亦然。(此时与修改非可变对象同理)
2.深复制

递归复制,存储空间独立。(消除了潜复制带来的隐患)

深复制构造一个新的复合对象,然后递归地将在原始对象中找到的对象插入到复合对象中。( 相当于找到最终的值类型,再独立开辟空间存储一份。)

>>>import copy
>>>aList = [1,[2],3]
>>>bList = copy.deepcopy(aList)		#深复制,aList和bList完全独立,互不影响

>>>aList[1].append(5)				#对[2]原地操作
>>>aList
[1,[2,5],3]		#修改bList不影响aList。反之亦然。(修改其他元素——可变或非可变对象 也是如此)
>>>bList
[1,[2],3]

Ⅱ、深复制存在两个问题:

  1. 递归对象(直接或间接保持对自身引用的复合对象)可能导致递归循环。
  2. 因为deep copy拷贝了所有的东西它可能拷贝了太多,例如管理数据结构甚至应该在opies之间共享。

Ⅲ、Python的深复制操作通过以下方法避免了这些问题:

  1. 在当前操作过程中保存已经复制的对象表。

  2. 让用户定义的类重写复制操作或复制的组件集。

    【 不复制模块、类、函数、方法、堆栈跟踪、堆栈帧、文件、套接字、窗口、数组等类型,也不复制任何类似的类型。类可以使用与控制pickle相同的接口来控制复制:它们可以定义称为余下的getinitargs__()、余下的getstate__()和余下的setstate__()的方法。“pickle”可以获得关于这些方法的信息。】

六、切片实例:

以列表 lst = [ 'a', 'b', 'c', 'd', 'e' ] 为例

1.切取整个列表

>>>lst[::1]
['a', 'b', 'c', 'd', 'e']

2.切取倒叙的列表

>>>lst[::-1]
['e', 'd', 'c', 'b', 'a']

3.省略step以及第二个冒号

>>>lst[:]		#step默认为1
['a', 'b', 'c', 'd', 'e']

4.正负索引混用

>>>lst[0:-2:1]
['a', 'b', 'c']

5.切片索引越界

>>>[-5:10:]
['a', 'b', 'c', 'd', 'e']

6.列表内的空区间

>>>lst[0:-5:]
[]

7.列表外的区间

>>>lst[5:10:]
[]
>>>lst[-5:-1:]
[]

8.start到end方向与step方向相反

>>>lst[1:3:-1]
[]

9.连续切片

>>>lst[0:4][0:3][1:4]		#可无限次连续切片操作
['b', 'c']

'''
等价于
>>> x = lst[0:4]
>>> y = x[0:3]
>>> z = y[1:4]
>>> z
['b', 'c']
'''

10.参数可用多项表达式

>>>lst[2+1:3*2:7%3] 	# 即:a[2+1:3*2:7%3] = a[3:6:1]
['d', 'e']

11.修改多个元素

>>>lst[1:3] = ['A','B','C']
['a', 'A', 'B', 'C', 'd', 'e']

【注意:】

>>>lst[-1:]		#此时step默认=1,因此end_index采用正索引默认值=len(lst)
['e']
  • 8
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Whitemeen太白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值