Skip to content

Python 切片:一行代码搞定"截取"的瑞士军刀

引言:切蛋糕的艺术

想象你面前有一条长长的瑞士卷蛋糕,你想:

  • 切最前面的 3 块给客人;
  • 切最后面的 2 块自己留着;
  • 每隔 1 块切 1 块,尝尝不同位置的味道;
  • 或者干脆切一条"从中间到结尾"的大块。

如果每次都要一块一块地数着切,那太麻烦了。你只需要告诉蛋糕师傅:"从第几块开始,到第几块结束,每隔几块切一块",师傅一刀下去,完事。

Python 的**切片(Slice)**就是这个"蛋糕师傅"。它用一行简洁的代码,就能从列表、元组、字符串中精确截取出你想要的任意部分。这是 Python 最常用、最优雅的特性之一,掌握它,你的代码会立刻简洁一倍。


一、为什么需要切片?从"笨办法"说起

1.1 问题的出现

假设有一个班级名单:

python
L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']

老师要叫前 3 个同学去办公室,怎么取出来?

笨办法 1:一个一个拿

python
>>> [L[0], L[1], L[2]]
['Michael', 'Sarah', 'Tracy']

问题:如果要取前 30 个呢?难道写 L[0], L[1], ..., L[29]?手指会断的。

笨办法 2:用循环

python
>>> r = []
>>> n = 3
>>> for i in range(n):
...     r.append(L[i])
...
>>> r
['Michael', 'Sarah', 'Tracy']

可以是可以,但 4 行代码干一件小事,太啰嗦了。

1.2 切片的解决方案

python
>>> L[0:3]
['Michael', 'Sarah', 'Tracy']

一行搞定。生活化理解:就像对蛋糕师傅说"给我第 0 块到第 3 块之间的蛋糕"(注意:不包含第 3 块),师傅手起刀落,正好 3 块。

这就是切片的核心语法:序列[开始索引:结束索引],取从开始到结束、但不包含结束的那一段。


二、切片语法详解:三个参数的魔法

切片的完整语法是:

python
序列[开始:结束:步长]

三个参数都可以省略,非常灵活。我们逐个拆解。

2.1 开始和结束:左闭右开区间

这是切片最容易犯错的地方,记住一句口诀:"含头不含尾"(左闭右开)。

python
L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
# 索引:  0          1        2        3       4
切片写法含义结果
L[0:3]从索引 0 到 3(不含 3)['Michael', 'Sarah', 'Tracy']
L[1:3]从索引 1 到 3(不含 3)['Sarah', 'Tracy']
L[:3]开头省略 = 从 0 开始['Michael', 'Sarah', 'Tracy']
L[2:]结尾省略 = 直到最后['Tracy', 'Bob', 'Jack']

生活化理解:"含头不含尾"就像电影院座位号"从 5 排 1 座到 5 排 10 座"通常意味着 1~9 座,因为 10 座是下一段的开始。这种设计让 L[0:n] 正好取 n 个元素,L[a:b] 的长度正好是 b - a,非常整齐。

2.2 负数索引:从尾巴开始数

Python 支持负数索引:-1 表示倒数第一个,-2 表示倒数第二个。切片同样支持:

python
>>> L[-2:]        # 从倒数第 2 个到最后
['Bob', 'Jack']

>>> L[-2:-1]      # 从倒数第 2 个到倒数第 1 个(不含)
['Bob']

>>> L[:-1]        # 从开头到倒数第 1 个(不含),即"去掉最后一个"
['Michael', 'Sarah', 'Tracy', 'Bob']

记忆技巧:想象一排人,正数是"从前往后数第几个",负数是"从后往前数第几个"。-1 永远是队尾那个人。

2.3 步长:跳着取

第三个参数是步长,表示"每隔几个取一个":

python
>>> L = list(range(10))   # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> L[:6:2]     # 前 6 个里,每隔 2 个取 1 个
[0, 2, 4]

>>> L[::2]      # 整个列表,每隔 2 个取 1 个
[0, 2, 4, 6, 8]

>>> L[1::2]     # 从索引 1 开始,每隔 2 个取 1 个
[1, 3, 5, 7, 9]

生活化理解:步长就像做广播体操时"第 1、3、5、7 排同学出列"——不是连着叫,而是跳着叫。

负数步长:步长还可以是负数,表示从右往左取

python
>>> L[::-1]     # 步长 -1,整个列表倒序
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

>>> L[5:1:-1]   # 从索引 5 倒着取到索引 1(不含 1)
[5, 4, 3, 2]

[::-1] 是反转列表/字符串的经典写法,面试常考。

2.4 什么都不写:完整复制

python
>>> L[:]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

[:] 会原样复制一份列表。注意这是浅拷贝——新列表和旧列表是两个独立的对象,修改互不影响(但如果元素本身是可变对象,比如列表里套列表,内部还是共享的)。

python
>>> a = [1, 2, 3]
>>> b = a[:]      # 复制一份
>>> b.append(4)
>>> a
[1, 2, 3]         # 原列表不受影响

三、切片的适用对象:不只是列表

切片不是列表的专利,Python 中所有"序列类型"都支持切片。

3.1 元组(tuple):切片结果还是元组

python
>>> (0, 1, 2, 3, 4, 5)[:3]
(0, 1, 2)

>>> ('a', 'b', 'c', 'd')[::2]
('a', 'c')

元组不可修改,但切片读取完全没问题。

3.2 字符串:每个字符就是一个元素

字符串可以看成"字符组成的列表",切片极其常用:

python
>>> 'ABCDEFG'[:3]
'ABC'

>>> 'ABCDEFG'[::2]
'ACEG'

>>> 'hello world'[::-1]
'dlrow olleh'

>>> phone = '13812345678'
>>> phone[:3] + '****' + phone[-4:]    # 手机号打码
'138****5678'

背景知识:很多编程语言为字符串提供了各种截取函数(如 Java 的 substring()、JavaScript 的 substr()),而 Python 没有单独的字符串截取函数——因为切片一个操作就全搞定了。这是 Python 设计哲学"用一种方法做好一件事"的体现。

3.3 range、bytes 等其他序列

python
>>> range(100)[10:20:3]
range(10, 20, 3)

>>> b'ABCDEF'[2:5]
b'CDE'

四、实战演练:0-99 数列的花式切法

用一个 100 个数字的列表,把所有切片技巧过一遍:

python
>>> L = list(range(100))
需求切片写法结果
前 10 个数L[:10][0, 1, ..., 9]
后 10 个数L[-10:][90, 91, ..., 99]
第 11~20 个数L[10:20][10, 11, ..., 19]
前 10 个数里每隔 2 个取 1 个L[:10:2][0, 2, 4, 6, 8]
所有数每隔 5 个取 1 个L::5L[::5][0, 5, 10, ..., 95]
整个列表倒序L[::-1][99, 98, ..., 0]
去掉首尾各一个L[1:-1][1, 2, ..., 98]

实战技巧总结

  • 取前 n 个:L[:n]
  • 取后 n 个:L[-n:]
  • 去掉前 n 个:L[n:]
  • 去掉后 n 个:L[:-n]
  • 反转:L[::-1]

五、真实应用场景

5.1 分页显示

网页上"每页显示 10 条"的分页功能,本质就是切片:

python
def get_page(items, page, page_size=10):
    """获取第 page 页的数据(页码从 1 开始)"""
    start = (page - 1) * page_size
    end = start + page_size
    return items[start:end]

articles = list(range(1, 96))   # 假设有 95 篇文章
print(get_page(articles, 1))    # 第 1 页:[1..10]
print(get_page(articles, 10))   # 第 10 页:[91..95],不够 10 条也正常返回

注意:切片越界不会报错articles[90:100] 不会抛异常,而是自动取到最后一个为止。这是切片比直接索引 L[95] 更安全的地方。

5.2 字符串处理:文件扩展名

python
filename = 'report.final.v2.pdf'

# 取扩展名(最后一个 . 之后的部分)
ext = filename[filename.rfind('.')+1:]     # 'pdf'

# 取文件名主体(最后一个 . 之前的部分)
name = filename[:filename.rfind('.')]      # 'report.final.v2'

5.3 数据抽样

从大量数据中均匀抽样:

python
data = list(range(10000))
sample = data[::100]    # 每隔 100 个取 1 个,共 100 个样本

5.4 队列/栈操作

python
queue = [1, 2, 3, 4, 5]

first = queue[0]        # 查看队首
queue = queue[1:]       # 出队:去掉第一个元素(简单场景可用,大数据量建议用 collections.deque)

六、经典练习:手写 trim() 函数

题目:利用切片实现一个 trim(s) 函数,去除字符串首尾的空格,不允许使用字符串自带的 strip() 方法。

python
def trim(s):
    # 找到左边第一个非空格字符的位置
    start = 0
    while start < len(s) and s[start] == ' ':
        start += 1

    # 找到右边第一个非空格字符的位置
    end = len(s)
    while end > start and s[end - 1] == ' ':
        end -= 1

    return s[start:end]

# 测试
if trim('  hello  ') != 'hello':
    print('测试失败!')
elif trim('  hello') != 'hello':
    print('测试失败!')
elif trim('hello  ') != 'hello':
    print('测试失败!')
elif trim('  hello world  ') != 'hello world':
    print('测试失败!')
elif trim('') != '':
    print('测试失败!')
elif trim('    ') != '':
    print('测试失败!')
else:
    print('测试成功!')

思路解析

  1. 用两个指针 startend 分别从头、从尾向中间收缩,跳过空格;
  2. 收缩完毕后,中间剩下的部分就是去空格后的内容;
  3. 最后用切片 s[start:end] 一刀取出。

这正好演示了切片的精髓:先算出边界,再一刀取出,而不是一个字符一个字符地搬。


七、常见误区与避坑指南

误区 1:以为 L[0:3] 包含索引 3

python
L = ['a', 'b', 'c', 'd', 'e']
L[0:3]   # 是 ['a', 'b', 'c'],不包含 'd'!

牢记含头不含尾(左闭右开)。判断长度的小技巧:L[a:b] 的长度 = b - a

误区 2:混淆切片和索引的越界行为

python
L = [1, 2, 3]

L[10]       # 报错!IndexError: list index out of range
L[10:20]    # 不报错,返回 []
L[1:100]    # 不报错,返回 [2, 3]

切片越界宽容,索引越界严格。这在写防御性代码时很有用——不用担心切片会抛异常。

误区 3:以为 b = a[:] 是深拷贝

python
a = [[1, 2], [3, 4]]
b = a[:]        # 浅拷贝
b[0].append(99)
print(a)        # [[1, 2, 99], [3, 4]]  原列表也被改了!

[:] 只复制了外层,内层的子列表还是共享的。需要完全独立时请用 copy.deepcopy()

误区 4:负数步长时搞反开始和结束

python
L = [0, 1, 2, 3, 4, 5]

L[1:5:-1]    # 结果:[]  空的!
L[5:1:-1]    # 结果:[5, 4, 3, 2]  正确

规律:步长为负时,"开始索引"必须大于"结束索引"(因为是从右往左走)。可以记成:步长为负,起点在右,终点在左

误区 5:对字符串用切片后试图修改

python
s = 'hello'
s[0] = 'H'        # 报错!字符串不可变
s = 'H' + s[1:]   # 正确:用切片+拼接生成新字符串

字符串和元组不可变,切片只能用于读取,不能用于赋值。列表则可以:

python
L = [1, 2, 3, 4, 5]
L[1:3] = ['a', 'b', 'c']   # 列表切片可以赋值,甚至可以改变长度
print(L)                    # [1, 'a', 'b', 'c', 4, 5]

误区 6:步长为 0

python
L[::0]   # 报错!ValueError: slice step cannot be zero

步长表示"每隔几个取一个",取 0 个没有意义,Python 直接拒绝。


八、动手练习

  1. 基础题:给定 s = 'abcdefghij',用切片分别取出 'cde''jih'(倒序)、'bdfhj'(偶数位置字符)。
  2. 进阶题:编写函数 is_palindrome(s),用一行切片代码判断字符串是否为回文(正读反读都一样,如 'level')。
  3. 挑战题:给定列表 L = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],不借助任何新变量,只用切片操作,把列表变成 [6, 7, 8, 9, 10, 1, 2, 3, 4, 5](即前后两半互换)。
  4. 思考题L[:]list(L) 都能复制列表,它们有区别吗?在什么场景下会有不同表现?(提示:想想元组)

小结

  • 切片语法序列[开始:结束:步长],取"含头不含尾"的一段;三个参数都可省略,默认值是"从头、到尾、步长 1"。
  • 负数索引-1 是最后一个元素,支持倒数切片,如 L[-2:]
  • 负数步长[::-1] 一键反转序列,是最经典的切片技巧。
  • 适用范围:列表、元组、字符串等所有序列类型都支持切片;Python 不需要单独的字符串截取函数。
  • 越界安全:切片越界不报错,自动截断;直接索引越界会抛 IndexError
  • 核心心法:切片把"循环逐个取元素"简化成了"算好边界,一刀取出",让代码更短、更清晰、更不易出错。