Python切片在不同场景的表示及CPython特殊行为原因探究
先看你的测试代码和结果:
l = [1, 2, 3, 4, 5] print(l is l[:]) # False t = (1, 2, 3, 4, 5) print(t is t[:]) # True print(t[1:] is t[1:]) # False print('aa'[1:] is 'aa'[1:]) # True print('aaa'[1:] is 'aaa'[1:]) # False
这些结果都是CPython实现层面的优化选择,并非Python语言规范强制要求,下面逐个拆解原因:
1. 列表切片:总是返回新对象
列表是可变类型,l[:]是创建原列表的浅拷贝。为了保证修改切片后的对象不会影响原列表,CPython必须生成新的列表实例,所以l is l[:]结果为False。
2. 元组切片:仅全切片复用原对象
元组是不可变类型,CPython对**全切片(t[:])**做了优化:直接返回原元组对象,因为不可变对象不会被修改,复用原对象没有风险,能节省内存和创建对象的开销。
但非全切片(比如t[1:])无法复用原对象——因为切片范围不是整个元组,必须创建新的元组来存储指定范围的元素指针,所以每次调用t[1:]都会生成新实例,导致t[1:] is t[1:]为False。
3. 字符串切片:依赖驻留机制与视图优化
字符串的切片行为分两种情况:
- 短字符串驻留:
'aa'[1:]得到的是长度为1的字符串'a',CPython会自动驻留(缓存)长度为0或1的字符串,以及编译期确定的常量字符串。所以两次切片得到的是同一个缓存对象,结果为True。 - 非驻留字符串:
'aaa'[1:]得到的是长度为2的'aa',不在默认驻留范围内,每次切片都会生成新的字符串实例,所以结果为False。
此外,字符串切片的高效性源于CPython的视图优化:字符串底层是连续的字节数组,切片不需要复制数据,只需记录原字符串的内存偏移和长度,相当于创建一个“视图”,因此内存和时间开销极低。
为什么元组不能像字符串那样高效切片?
元组的元素可以是任意Python对象,底层存储的是元素的指针数组,而非连续的单一数据缓冲区。即使元组不可变,切片时也必须复制所有元素的指针到新的元组对象中,无法像字符串那样复用原内存做视图优化。这是元组内存布局特性决定的,而非仅仅因为不可变。
这些规则是所有Python实现都遵循的吗?
不是。这些都是CPython的具体实现细节,Python语言规范只规定了切片的语义(比如返回原序列的副本),但并未强制要求是否复用原对象或做视图优化。例如PyPy对元组切片也实现了视图优化,此时t[1:]可能会返回同一个对象,性能表现更接近字符串。
内容的提问来源于stack exchange,提问作者ByteEater

