一、基础语法与直观理解

1.1 基本格式

切片的语法格式是 sequence[start:stop:step],三个参数均可省略。

1.2 "左闭右开"原则

切片遵循数学区间 [start, stop) 的约定——包含 start,不包含 stop

1
2
nums = [0, 1, 2, 3, 4]
print(nums[1:3]) # [1, 2] —— 取索引1和2,不取3

这个设计的好处是:stop - start 恰好等于切片的长度,计算起来非常自然。

1.3 负数索引

负数索引从序列末尾倒数:-1 是最后一个元素,-2 是倒数第二个,以此类推。

1
2
3
4
nums = [0, 1, 2, 3, 4]
print(nums[-1]) # 4 —— 最后一个元素
print(nums[-3:-1]) # [2, 3] —— 倒数第三到倒数第二
print(nums[-3:]) # [2, 3, 4] —— 倒数第三到末尾

二、进阶操作与技巧

2.1 步长的奥秘

step 控制切片的方向和跨度:

1
2
3
4
5
6
7
8
9
10
nums = [0, 1, 2, 3, 4, 5, 6, 7]

# step 为正:从左往右
print(nums[::2]) # [0, 2, 4, 6] —— 每隔一个取
print(nums[1::2]) # [1, 3, 5, 7] —— 从索引1开始每隔一个取

# step 为负:从右往左
print(nums[::-1]) # [7, 6, 5, 4, 3, 2, 1, 0] —— 翻转
print(nums[::-2]) # [7, 5, 3, 1] —— 从右往左每隔一个取
print(nums[5:1:-1]) # [5, 4, 3, 2] —— 从索引5往左到索引2

关键规则:step 为正时,start 应在 stop 左边;step 为负时,start 应在 stop 右边,否则结果为空。

2.2 省略写法

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

nums[:] # [0, 1, 2, 3, 4] —— 完整拷贝
nums[2:] # [2, 3, 4] —— 从索引2到末尾
nums[:3] # [0, 1, 2] —— 从开头到索引2
nums[::2] # [0, 2, 4] —— 从开头到末尾,步长2

省略 start 默认从开头(或末尾,如果 step 为负),省略 stop 默认到末尾。

三、底层原理与内存模型

3.1 浅拷贝

切片操作创建的是浅拷贝——新列表是一个独立对象,但内部的元素仍然是原对象的引用:

1
2
3
4
5
6
original = [1, 2, 3]
copied = original[:]

print(copied) # [1, 2, 3]
print(original is copied) # False —— 不同的列表对象
print(original == copied) # True —— 值相同

修改外层元素互不影响:

1
2
3
copied[0] = 99
print(original) # [1, 2, 3] —— 原列表不受影响
print(copied) # [99, 2, 3]

3.2 引用机制:内部可变对象

如果列表中包含可变对象(如嵌套列表),浅拷贝只复制了引用,修改内部元素会影响原列表:

1
2
3
4
5
6
7
original = [[1, 2], [3, 4]]
copied = original[:]

# 修改内层列表
copied[0].append(99)
print(original) # [[1, 2, 99], [3, 4]] —— 原列表也被改了!
print(copied) # [[1, 2, 99], [3, 4]]

图解:

1
2
3
4
5
original ──→ [ ref1, ref2 ]     copied ──→ [ ref1', ref2' ]
| | | |
v v v v
[1,2,99] [3,4] [1,2,99] [3,4]
(同一个内层对象) (同一个内层对象)

ref1ref1' 指向同一个内层列表,所以通过任何一个引用修改,另一个也能看到。

3.3 深拷贝

如需完全独立,使用 copy.deepcopy()

1
2
3
4
5
6
7
8
import copy

original = [[1, 2], [3, 4]]
copied = copy.deepcopy(original)

copied[0].append(99)
print(original) # [[1, 2], [3, 4]] —— 原列表不受影响
print(copied) # [[1, 2, 99], [3, 4]]

3.4 不可变序列的切片

字符串和元组切片返回同类型的新对象,但由于它们不可变,不存在浅拷贝的"共享修改"问题:

1
2
3
4
5
s = "Hello"
print(s[1:4]) # 'ell' —— 新字符串

t = (1, 2, 3)
print(t[::-1]) # (3, 2, 1) —— 新元组

四、切片的赋值与修改

切片赋值可以对列表进行"批量修改"、"插入"和"删除":

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
nums = [0, 1, 2, 3, 4]

# 批量替换
nums[1:3] = [20, 30]
print(nums) # [0, 20, 30, 3, 4]

# 插入(替换空切片)
nums[1:1] = [10, 15]
print(nums) # [0, 10, 15, 20, 30, 3, 4]

# 删除(赋空值)
nums[2:5] = []
print(nums) # [0, 10, 3, 4]

# 用 del 删除
del nums[1:3]
print(nums) # [0, 4]

关键点:切片赋值的右侧可以是任何可迭代对象,长度不必与被替换的切片相同——这正是它灵活的原因。

五、实战与避坑指南

5.1 分页处理

1
2
3
4
5
6
7
data = list(range(1, 101))  # 1到100
page_size = 10
page = 3 # 第3页

start = (page - 1) * page_size
stop = page * page_size
print(data[start:stop]) # [21, 22, 23, 24, 25, 26, 27, 28, 29, 30]

5.2 数据清洗

1
2
3
4
raw = "  Hello, World!  "
cleaned = raw.strip() # 去除首尾空白
first_word = cleaned[:5] # 取前5个字符
print(first_word) # 'Hello'

5.3 常见陷阱

切片不会 IndexError:直接索引越界会报错,但切片越界只会"截断":

1
2
3
4
5
6
7
8
nums = [0, 1, 2]

# 直接索引越界 —— 报错
# print(nums[10]) # IndexError: list index out of range

# 切片越界 —— 安全截断
print(nums[1:10]) # [1, 2] —— 不会报错
print(nums[5:10]) # [] —— 也不会报错

步长为负时的 start/stop

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

# 常见错误:step 为负但 start 在 stop 左边
print(nums[1:4:-1]) # [] —— 空列表!因为从左往右无法到达

# 正确写法
print(nums[4:1:-1]) # [4, 3, 2] —— 从右往左

六、总结表格

切片写法 效果 说明
s[a:b] 取索引 a 到 b-1 左闭右开
s[a:] 从索引 a 到末尾 省略 stop
s[:b] 从开头到索引 b-1 省略 start
s[:] 完整浅拷贝 省略全部
s[::2] 每隔一个取 步长2
s[::-1] 翻转序列 步长-1
s[a:b:c] 从 a 到 b-1,步长 c 完整语法
s[-3:] 最后3个元素 负数索引
s[-3:-1] 倒数第3到倒数第2 负数切片

切片是 Python 中最优雅的特性之一。理解了"左闭右开"、步长方向和浅拷贝的本质,你就能在数据处理中游刃有余。