Python

Python 切片语法中容易踩的坑

Python 切片语法中容易踩的坑

切片(slicing)是 Python 里最常用也最容易"看起来懂了,实际用错"的语法之一。本文梳理切片机制本身的规则,以及实践中最容易踩的坑。

1. 基础语法与默认值

a[start:stop:step] 中三个参数都可省略:

a = [0, 1, 2, 3, 4, 5]
a[2:4]    # [2, 3]
a[:3]     # [0, 1, 2],start 默认为 0
a[3:]     # [3, 4, 5],stop 默认为序列末尾
a[:]      # 整个序列的拷贝

注意 stop 是不包含的(左闭右开),这是切片里最基础但最容易导致差一位错误(off-by-one)的地方。

2. 切片不会越界报错

这是切片和普通索引最大的区别:普通索引越界会抛 IndexError,但切片会自动截断到合法范围,不报错:

a = [0, 1, 2]
a[10]      # IndexError
a[1:100]   # [1, 2],不会报错,自动截断
a[100:200] # [],越界范围直接返回空列表

这个"宽容"特性方便但也容易隐藏 bug:本以为切片越界会报错从而提醒你检查范围,结果它默默返回空结果。

3. 负数索引与负数步长

负数索引表示从末尾数起,-1 是最后一个元素:

a[-1]     # 5,最后一个元素
a[-3:]    # [3, 4, 5],最后三个
a[:-2]    # [0, 1, 2, 3],去掉最后两个

step 为负数时,startstop 的默认值会反转——start 默认是序列末尾,stop 默认是序列开头之前(而不是 0):

a[::-1]    # [5, 4, 3, 2, 1, 0],整体反转
a[4:1:-1]  # [4, 3, 2],从索引4倒着取到索引1(不含1)

容易踩的坑是:当 step 为负数时,如果还写成正向的 start < stop(比如 a[1:4:-1]),结果会是空列表,因为方向和范围矛盾。

4. 切片返回的是新对象,不是视图

对原生 listtuplestr 做切片,得到的是一份新对象(浅拷贝),修改切片结果不会影响原序列:

a = [1, 2, 3]
b = a[:]
b.append(4)
print(a)   # [1, 2, 3],a 没有变化

但要注意"浅"拷贝的含义:如果元素本身是可变对象(比如嵌套列表),内层对象仍然是共享引用:

a = [[1, 2], [3, 4]]
b = a[:]
b[0].append(99)
print(a)   # [[1, 2, 99], [3, 4]],内层列表被一起改了

如果需要完全独立的拷贝,应使用 copy.deepcopy()

注意这一点和 NumPy 数组不同:对 numpy.ndarray 切片得到的是视图,修改切片会直接改动原数组,这是从 list 转到 numpy 时最容易踩的坑之一。

5. 字符串/元组是不可变的,切片赋值会报错

切片赋值(替换内容)只对可变序列(如 list)有效,对 strtuple 这类不可变类型切片赋值会直接报错:

s = "hello"
s[1:3] = "EL"   # TypeError,字符串不支持切片赋值

6. 列表切片赋值可以改变长度

这是切片赋值最特殊也最容易被忽视的一点:右侧序列的长度不需要和切片范围一致,赋值后列表长度会跟着变化:

a = [0, 1, 2, 3, 4]
a[1:3] = [9, 9, 9, 9]   # 用4个元素替换原来2个元素
print(a)   # [0, 9, 9, 9, 9, 3, 4],长度变长了

a[1:5] = []             # 用空列表替换,等价于删除这段
print(a)   # [0, 4]

也可以用 del a[start:stop] 直接删除一段元素,效果和上面的空列表赋值相同。

7. 切片赋值要求右侧是可迭代对象

a = [1, 2, 3]
a[1:2] = 5   # TypeError,必须是可迭代对象(如 [5])
a[1:2] = [5]  # 正确

新手常见错误是直接赋一个标量值,而不是包在列表里。

8. step 为 0 会报错

a[::0]   # ValueError: slice step cannot be zero

9. slice() 对象与 indices() 方法

切片语法 a[start:stop:step] 背后实际是构造了一个 slice 对象,可以显式创建并复用:

s = slice(1, 4, 2)
a[s]   # 等价于 a[1:4:2]

slice.indices(length) 可以把负数、越界、省略值统一规范化成实际的 (start, stop, step) 三元组,自己实现支持切片的类时很有用。

小结清单

  • 切片左闭右开,越界不报错,会自动截断。
  • step 为负时,start/stop 的默认方向也会反转,注意方向与范围是否匹配。
  • 原生序列切片是浅拷贝,嵌套可变对象仍共享引用;NumPy 数组切片则是视图,行为不同。
  • 不可变类型(str、tuple)不支持切片赋值。
  • 列表切片赋值的右侧长度可以和切片范围不同,因此可以用来插入、替换、删除元素。
  • 切片赋值右侧必须是可迭代对象,不能是单个标量。
  • step 不能为 0。
  • 复杂场景可以用 slice() 对象和 indices() 方法显式处理切片逻辑。