← 返回题目列表

Python 切片怎么用?切片会不会产生新对象?

高频 简单 第 2 / 22 题 更新于 2026/07/25
Python切片序列list

简化版

切片语法是 seq[start:stop:step],表示从 start 开始,到 stop 之前结束,按 step 步长取元素。对 list、tuple、str 这类序列做普通切片通常会产生新对象,但新容器里的元素引用仍可能和原容器共享。

详细版

基本语法:

nums = [0, 1, 2, 3, 4, 5]

print(nums[1:4])    # [1, 2, 3]
print(nums[:3])     # [0, 1, 2]
print(nums[3:])     # [3, 4, 5]
print(nums[::2])    # [0, 2, 4]
print(nums[::-1])   # [5, 4, 3, 2, 1, 0]

关键点:

  • start 默认是开头;
  • stop 默认是结尾;
  • step 默认是 1;
  • stop 不包含;
  • 负数下标从末尾开始;
  • [::-1] 常用于反转序列。

切片会创建新容器:

a = [1, 2, 3]
b = a[:]
print(a is b)  # False

但如果元素本身是可变对象,内部对象仍然共享:

a = [[1], [2]]
b = a[:]
b[0].append(9)
print(a)  # [[1, 9], [2]]

完整版教学

一、切片的区间是左闭右开

seq[start:stop] 取的是:

start <= index < stop

例如:

s = "abcdef"
print(s[1:4])  # bcd

包含下标 1、2、3,不包含 4。左闭右开的好处是:

  • s[:i]s[i:] 正好能无重叠拼回原序列;
  • 切片长度容易计算:stop - start
  • 边界更统一。

数字例子:s = "abcdef"s[1:4] 的长度是 4 - 1 = 3,结果正好是下标 1、2、3 的 "bcd"s[:3] + s[3:] 会得到原字符串,因为前者取 0、1、2,后者从 3 开始,没有重叠也没有空洞。

二、省略 start、stop、step 的含义

nums = [0, 1, 2, 3, 4]

nums[:3]    # 从开头到 3 之前
nums[2:]    # 从 2 到结尾
nums[:]     # 复制整个序列
nums[::2]   # 每隔一个取一个
nums[::-1]  # 反向复制

step 可以为负数。负数步长表示反向走:

print(nums[4:1:-1])  # [4, 3, 2]

这里从下标 4 开始,反向走到下标 1 之后停止,不包含下标 1。

可以把 step 理解成“游标每次走几格”。正数从左往右,负数从右往左;step 不能为 0,因为游标不前进就无法遍历。负步长时,默认起点会变成末尾附近,默认终点会越过开头,所以 [::-1] 能反转整个序列。

nums = [0, 1, 2, 3, 4]
index  0  1  2  3  4

nums[4:1:-1] 走 4 -> 3 -> 2,停在 1 之前

三、切片会创建新容器,但通常是浅拷贝

切片通常返回同类序列:

print("abc"[1:])      # "bc"
print((1, 2, 3)[1:])  # (2, 3)
print([1, 2, 3][1:])  # [2, 3]

但要注意,切片复制的是容器结构,不一定深度复制元素对象。对 list 来说,a[:] 会创建一个新列表,外层列表身份不同;里面的元素引用会被拷过去,如果元素本身是可变对象,内部仍然共享。

users = [{"name": "Tom"}, {"name": "Jerry"}]
new_users = users[:]

new_users[0]["age"] = 18
print(users[0])  # {'name': 'Tom', 'age': 18}

这是浅拷贝效果:外层列表是新的,里面的字典还是同一个。

users      ──> [ ref0, ref1 ]
                 |     |
new_users  ──> [ ref0, ref1 ]
                 v     v
              dict0  dict1

所以 a[:] 常用于复制外层列表,但不能替代 copy.deepcopy()。字符串和 tuple 的切片也会返回序列对象;由于它们不可变,解释器在某些完整切片场景下可能复用对象,但语义上你不应该依赖身份,只关心值即可。

四、切片赋值是 list 的强大特性

列表支持切片赋值:

nums = [1, 2, 3, 4]
nums[1:3] = [20, 30, 40]
print(nums)  # [1, 20, 30, 40, 4]

也可以删除一段:

nums = [1, 2, 3, 4]
nums[1:3] = []
print(nums)  # [1, 4]

字符串和 tuple 不支持这种原地修改,因为它们不可变。

切片赋值的右侧长度不必和左侧一样,这使它可以完成插入、替换、删除。比如 nums[1:1] = [9, 9] 表示在下标 1 前插入两个元素;nums[::2] = ... 这类带步长的扩展切片赋值则要求长度匹配,否则会报错,因为它是在替换固定位置。

操作示例含义
替换a[1:3] = [8, 9]替换一段
插入a[1:1] = [8, 9]在位置 1 插入
删除a[1:3] = []删除一段
反转副本a[::-1]生成反向新列表

五、越界和负下标怎么理解

易错点一:切片越界不会像普通下标那样报错。

nums = [1, 2, 3]
print(nums[:100])  # [1, 2, 3]

但普通下标越界会报错:

# nums[100]  # IndexError

这是因为切片会把越界范围裁剪到合法区间。nums[:100] 等价于“从开头尽量取到第 100 个之前”,列表只有 3 个元素,就返回全部。普通下标 nums[100] 要求第 100 个位置真实存在,不存在就报错。

负下标也只是从末尾换算位置:长度为 6 的序列里,-1 是下标 5,-2 是下标 4。s[-4:-1]"abcdef" 来说就是下标 2、3、4,结果为 "cde"。负数和左闭右开并不冲突,只是先换算大致位置,再按切片规则取。

六、性能和使用边界

切片很方便,但它会创建新序列或修改原列表,因此要考虑数据规模。big_list[::-1] 会创建一个反向新列表,如果原列表有 100 万个元素,新列表也要保存 100 万个引用。只想反向迭代时,可以用 reversed(big_list),它返回迭代器,不会立刻复制整个列表。

for x in reversed(big_list):
    ...

同理,seq[a:b] 在循环里频繁切大段数据,可能造成大量临时对象。算法题中切片写法简洁,但如果追求最优空间复杂度,要看是否可以用下标边界代替复制子序列。

易错点:a[:] 复制的是外层容器,不是深拷贝;[::-1] 生成反向结果,也不是零成本视图。

七、常见误区与追问

  • 误区:切片的 stop 会被包含。 Python 切片是左闭右开,stop 不包含,长度更容易计算。
  • 误区:a[:] 是深拷贝。 它通常只复制外层容器,内部可变对象仍可能共享。
  • 误区:切片越界一定报错。 切片会裁剪到合法范围,普通下标越界才会 IndexError
  • 追问:[::-1]reversed() 有什么区别? 前者创建反向新序列,后者返回反向迭代器,更省内存。
  • 追问:切片赋值为什么能改变列表长度? 普通切片替换的是一个区间,右侧序列长度可以不同,所以能插入或删除。
  • 追问:负步长时为什么 nums[4:1:-1] 不含下标 1? 左闭右开规则仍成立,只是方向反过来,走到 stop 之前停止。

八、加强记忆

把切片记成“三个旋钮”:start 控起点,stop 控终点但不包含,step 控方向和步长。普通切片会生成新序列或新外层容器,列表切片赋值能原地替换区间;遇到负下标、反转和越界时,先画下标轴,再按左闭右开和步长方向走。