03.python中的切片

Python 中的切片详解

1. 前言

1.1 什么是切片(Slicing)

切片(Slicing)是 Python 中一种非常强大且常用的操作,它允许你从序列类型(如字符串、列表、元组等)中提取一部分元素,而不需要修改原始数据。切片操作返回一个新的序列,原始数据保持不变。

1.2 为什么需要学习切片

  • 提高效率:快速获取序列的一部分,无需循环
  • 代码简洁:一行代码就能完成复杂的提取操作
  • 功能强大:支持正索引、负索引、步长等多种操作
  • 广泛应用:在数据处理、字符串操作、列表处理中经常使用

1.3 哪些数据类型支持切片

Python 中支持切片操作的数据类型包括:

  • 字符串(str)"Hello World"
  • 列表(list)[1, 2, 3, 4, 5]
  • 元组(tuple)(1, 2, 3, 4, 5)
  • 字节序列(bytes)b'Hello'
  • 字节数组(bytearray)bytearray(b'Hello')
  • 范围(range)range(10)(Python 3.2+)

2. 切片的基本语法

2.1 基本语法格式

切片的语法格式如下:

sequence[start:stop:step]

参数说明

  • start:起始索引(包含),默认为序列的开始(0)
  • stop:结束索引(不包含),默认为序列的结束
  • step:步长(可选),默认为 1,表示每次取一个元素

重要提示

  • 切片操作返回的是新的序列,不会修改原始数据
  • 结束索引是不包含的,即 [start:stop] 取的是从 startstop-1 的元素

2.2 最简单的切片示例

# 创建一个列表
my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 获取索引 2 到 5 的元素(不包含索引 5)
result = my_list[2:5]
print(result)  # 输出: [2, 3, 4]

# 原始列表不变
print(my_list)  # 输出: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

3. 索引的基础知识

3.1 正索引(正向索引)

正索引从 0 开始,从左到右递增:

my_list = ['a', 'b', 'c', 'd', 'e']
#          0    1    2    3    4    <- 正索引

print(my_list[0])  # 输出: 'a'
print(my_list[1])  # 输出: 'b'
print(my_list[4])  # 输出: 'e'

3.2 负索引(反向索引)

负索引从 -1 开始,从右到左递减:

my_list = ['a', 'b', 'c', 'd', 'e']
#         -5   -4   -3   -2   -1    <- 负索引

print(my_list[-1])  # 输出: 'e'(最后一个元素)
print(my_list[-2])  # 输出: 'd'(倒数第二个元素)
print(my_list[-5])  # 输出: 'a'(第一个元素)

3.3 索引示意图

正索引:  0    1    2    3    4    5    6    7    8    9
序列:   [a]  [b]  [c]  [d]  [e]  [f]  [g]  [h]  [i]  [j]
负索引: -10  -9   -8   -7   -6   -5   -4   -3   -2   -1

4. 切片的各种用法

4.1 基本切片:[start:stop]

语法sequence[start:stop]

从索引 start 开始,到索引 stop-1 结束(不包含 stop)。

# 示例 1:使用正索引
my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print(my_list[2:6])    # 输出: [2, 3, 4, 5]
print(my_list[0:3])   # 输出: [0, 1, 2]
print(my_list[5:10])  # 输出: [5, 6, 7, 8, 9]

# 示例 2:使用负索引
print(my_list[-5:-1])  # 输出: [5, 6, 7, 8]
print(my_list[-3:-1])  # 输出: [7, 8]

# 示例 3:混合使用正负索引
print(my_list[2:-2])   # 输出: [2, 3, 4, 5, 6, 7]
print(my_list[-5:8])   # 输出: [5, 6, 7]

重要提示

  • 如果 start >= stop,返回空序列
  • 如果索引超出范围,不会报错,会自动调整到有效范围
my_list = [0, 1, 2, 3, 4]

print(my_list[3:1])    # 输出: [](空列表,因为 3 >= 1)
print(my_list[1:100])  # 输出: [1, 2, 3, 4](自动调整到列表末尾)
print(my_list[-100:3]) # 输出: [0, 1, 2](自动调整到列表开头)

4.2 省略起始索引:[:stop]

语法sequence[:stop]

从序列开头到索引 stop-1 结束。

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

print(my_list[:5])   # 输出: [0, 1, 2, 3, 4](前 5 个元素)
print(my_list[:3])   # 输出: [0, 1, 2](前 3 个元素)
print(my_list[:0])   # 输出: [](空列表)

# 使用负索引
print(my_list[:-3])  # 输出: [0, 1, 2, 3, 4, 5, 6](除了最后 3 个元素)
print(my_list[:-1])  # 输出: [0, 1, 2, 3, 4, 5, 6, 7, 8](除了最后一个元素)

4.3 省略结束索引:[start:]

语法sequence[start:]

从索引 start 开始到序列末尾。

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

print(my_list[5:])   # 输出: [5, 6, 7, 8, 9](从索引 5 到末尾)
print(my_list[3:])   # 输出: [3, 4, 5, 6, 7, 8, 9]
print(my_list[0:])   # 输出: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9](整个列表)

# 使用负索引
print(my_list[-3:])  # 输出: [7, 8, 9](最后 3 个元素)
print(my_list[-1:])  # 输出: [9](最后一个元素)

4.4 省略起始和结束索引:[:]

语法sequence[:]

获取整个序列的副本(浅拷贝)。

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

# 获取整个列表的副本
copy_list = my_list[:]
print(copy_list)  # 输出: [0, 1, 2, 3, 4, 5]

# 修改副本不会影响原列表
copy_list[0] = 999
print(copy_list)  # 输出: [999, 1, 2, 3, 4, 5]
print(my_list)    # 输出: [0, 1, 2, 3, 4, 5](原列表不变)

注意:对于列表,[:] 是浅拷贝。如果列表包含可变对象(如嵌套列表),需要深拷贝。

# 浅拷贝示例
original = [[1, 2], [3, 4]]
shallow_copy = original[:]

# 修改浅拷贝中的嵌套列表会影响原列表
shallow_copy[0][0] = 999
print(original)      # 输出: [[999, 2], [3, 4]]
print(shallow_copy)  # 输出: [[999, 2], [3, 4]]

4.5 带步长的切片:[start:stop:step]

语法sequence[start:stop:step]

start 开始,每隔 step 个元素取一个,直到 stop-1

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

# 步长为 2:每隔一个元素取一个
print(my_list[0:10:2])  # 输出: [0, 2, 4, 6, 8]
print(my_list[1:10:2])  # 输出: [1, 3, 5, 7, 9]

# 步长为 3:每隔两个元素取一个
print(my_list[0:10:3])  # 输出: [0, 3, 6, 9]
print(my_list[1:10:3])  # 输出: [1, 4, 7]

# 省略起始和结束,只指定步长
print(my_list[::2])     # 输出: [0, 2, 4, 6, 8](整个列表,步长为 2)
print(my_list[::3])     # 输出: [0, 3, 6, 9](整个列表,步长为 3)

4.6 负步长切片:反向提取

语法sequence[start:stop:step](step 为负数)

当步长为负数时,切片会从右向左提取元素。

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

# 步长为 -1:反转整个列表
print(my_list[::-1])    # 输出: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

# 步长为 -2:从右向左,每隔一个元素取一个
print(my_list[::-2])    # 输出: [9, 7, 5, 3, 1]

# 指定起始和结束位置(注意:使用负步长时,start 应该大于 stop)
print(my_list[8:2:-1])  # 输出: [8, 7, 6, 5, 4, 3]
print(my_list[9:0:-2])  # 输出: [9, 7, 5, 3, 1]

# 从末尾到开头
print(my_list[9::-1])   # 输出: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
print(my_list[:3:-1])   # 输出: [9, 8, 7, 6, 5, 4](从末尾到索引 3,不包含索引 3)

重要提示

  • 使用负步长时,start 必须大于 stop,否则返回空序列
  • 使用负步长时,索引的含义会发生变化
my_list = [0, 1, 2, 3, 4]

# 错误示例:start < stop,返回空列表
print(my_list[1:4:-1])  # 输出: []

# 正确示例:start > stop
print(my_list[4:1:-1])  # 输出: [4, 3, 2]

5. 不同数据类型的切片示例

5.1 字符串切片

字符串也支持切片操作,返回新的字符串。

# 基本字符串切片
text = "Hello World"

print(text[0:5])      # 输出: "Hello"
print(text[6:11])     # 输出: "World"
print(text[:5])       # 输出: "Hello"
print(text[6:])       # 输出: "World"
print(text[::-1])     # 输出: "dlroW olleH"(反转字符串)

# 提取子字符串
email = "user@example.com"
username = email[:4]           # 输出: "user"
domain = email[5:]             # 输出: "example.com"
at_symbol = email[4:5]         # 输出: "@"

# 每隔一个字符提取
print(text[::2])      # 输出: "HloWrd"(每隔一个字符)
print(text[1::2])     # 输出: "el ol"(从索引 1 开始,每隔一个字符)

# 反转字符串的几种方法
text = "Python"
print(text[::-1])              # 方法 1:使用切片
print(''.join(reversed(text))) # 方法 2:使用 reversed() 函数

5.2 列表切片

列表切片是最常用的操作之一。

# 基本列表切片
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 获取前 5 个元素
first_five = numbers[:5]
print(first_five)  # 输出: [0, 1, 2, 3, 4]

# 获取后 5 个元素
last_five = numbers[-5:]
print(last_five)   # 输出: [5, 6, 7, 8, 9]

# 获取中间的元素
middle = numbers[3:7]
print(middle)      # 输出: [3, 4, 5, 6]

# 每隔一个元素取一个
even_indices = numbers[::2]
print(even_indices)  # 输出: [0, 2, 4, 6, 8]

odd_indices = numbers[1::2]
print(odd_indices)   # 输出: [1, 3, 5, 7, 9]

# 反转列表
reversed_numbers = numbers[::-1]
print(reversed_numbers)  # 输出: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

5.3 元组切片

元组也支持切片,返回新的元组。

# 元组切片
my_tuple = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

print(my_tuple[2:6])   # 输出: (2, 3, 4, 5)
print(my_tuple[:5])    # 输出: (0, 1, 2, 3, 4)
print(my_tuple[5:])    # 输出: (5, 6, 7, 8, 9)
print(my_tuple[::-1])  # 输出: (9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
print(my_tuple[::2])   # 输出: (0, 2, 4, 6, 8)

5.4 字符串、列表、元组的对比

# 字符串
text = "Python"
print(text[1:4])      # 输出: "yth"
print(type(text[1:4]))  # 输出: <class 'str'>

# 列表
my_list = ['P', 'y', 't', 'h', 'o', 'n']
print(my_list[1:4])   # 输出: ['y', 't', 'h']
print(type(my_list[1:4]))  # 输出: <class 'list'>

# 元组
my_tuple = ('P', 'y', 't', 'h', 'o', 'n')
print(my_tuple[1:4])  # 输出: ('y', 't', 'h')
print(type(my_tuple[1:4]))  # 输出: <class 'tuple'>

6. 切片的常见应用场景

6.1 获取序列的前 N 个元素

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

# 获取前 3 个元素
first_three = my_list[:3]
print(first_three)  # 输出: [1, 2, 3]

# 获取前 5 个元素
first_five = my_list[:5]
print(first_five)   # 输出: [1, 2, 3, 4, 5]

6.2 获取序列的后 N 个元素

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

# 获取后 3 个元素
last_three = my_list[-3:]
print(last_three)   # 输出: [8, 9, 10]

# 获取后 5 个元素
last_five = my_list[-5:]
print(last_five)    # 输出: [6, 7, 8, 9, 10]

6.3 获取序列的中间部分

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

# 获取中间的元素(去掉第一个和最后一个)
middle = my_list[1:-1]
print(middle)       # 输出: [2, 3, 4, 5, 6, 7, 8, 9]

# 获取索引 2 到 7 的元素
middle_part = my_list[2:8]
print(middle_part)  # 输出: [3, 4, 5, 6, 7, 8]

6.4 反转序列

# 反转字符串
text = "Python"
reversed_text = text[::-1]
print(reversed_text)  # 输出: "nohtyP"

# 反转列表
my_list = [1, 2, 3, 4, 5]
reversed_list = my_list[::-1]
print(reversed_list)  # 输出: [5, 4, 3, 2, 1]

# 反转元组
my_tuple = (1, 2, 3, 4, 5)
reversed_tuple = my_tuple[::-1]
print(reversed_tuple)  # 输出: (5, 4, 3, 2, 1)

6.5 提取偶数索引或奇数索引的元素

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

# 提取偶数索引的元素(索引 0, 2, 4, 6, 8)
even_indices = my_list[::2]
print(even_indices)  # 输出: [0, 2, 4, 6, 8]

# 提取奇数索引的元素(索引 1, 3, 5, 7, 9)
odd_indices = my_list[1::2]
print(odd_indices)   # 输出: [1, 3, 5, 7, 9]

6.6 字符串处理:提取文件名、扩展名等

# 提取文件名(不含扩展名)
file_path = "document.pdf"
# 找到最后一个点的位置
dot_index = file_path.rfind('.')
if dot_index != -1:
    filename = file_path[:dot_index]
    extension = file_path[dot_index+1:]
    print(f"文件名: {filename}")      # 输出: 文件名: document
    print(f"扩展名: {extension}")      # 输出: 扩展名: pdf

# 提取 URL 的不同部分
url = "https://www.example.com/page"
protocol = url[:5]           # "https"
domain = url[8:21]           # "www.example.com"
path = url[21:]              # "/page"

# 提取日期部分
date_string = "2024-01-15"
year = date_string[:4]       # "2024"
month = date_string[5:7]     # "01"
day = date_string[8:]        # "15"

6.7 列表的分块处理

# 将列表分成固定大小的块
def chunk_list(lst, chunk_size):
    """将列表分成指定大小的块"""
    return [lst[i:i+chunk_size] for i in range(0, len(lst), chunk_size)]

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

# 分成每块 3 个元素
chunks = chunk_list(my_list, 3)
print(chunks)  # 输出: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

# 分成每块 4 个元素
chunks = chunk_list(my_list, 4)
print(chunks)  # 输出: [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]

6.8 删除序列的某些元素(通过切片创建新序列)

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

# 删除第一个元素
without_first = my_list[1:]
print(without_first)  # 输出: [2, 3, 4, 5, 6, 7, 8, 9, 10]

# 删除最后一个元素
without_last = my_list[:-1]
print(without_last)   # 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]

# 删除前两个元素
without_first_two = my_list[2:]
print(without_first_two)  # 输出: [3, 4, 5, 6, 7, 8, 9, 10]

# 删除后两个元素
without_last_two = my_list[:-2]
print(without_last_two)   # 输出: [1, 2, 3, 4, 5, 6, 7, 8]

# 删除第一个和最后一个元素
without_ends = my_list[1:-1]
print(without_ends)   # 输出: [2, 3, 4, 5, 6, 7, 8, 9]

7. 切片赋值(修改序列)

7.1 列表的切片赋值

切片不仅可以读取,还可以用于修改列表(注意:字符串和元组不支持切片赋值,因为它们是不可变的)。

# 替换列表的一部分
my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 替换索引 2 到 5 的元素
my_list[2:6] = [20, 30, 40, 50]
print(my_list)  # 输出: [0, 1, 20, 30, 40, 50, 6, 7, 8, 9]

# 替换的元素数量可以不同
my_list = [0, 1, 2, 3, 4, 5]
my_list[2:4] = [20, 30, 40]  # 用 3 个元素替换 2 个元素
print(my_list)  # 输出: [0, 1, 20, 30, 40, 4, 5]

my_list = [0, 1, 2, 3, 4, 5]
my_list[2:5] = [20]  # 用 1 个元素替换 3 个元素
print(my_list)  # 输出: [0, 1, 20, 5]

# 删除元素:用空列表替换
my_list = [0, 1, 2, 3, 4, 5]
my_list[2:4] = []
print(my_list)  # 输出: [0, 1, 4, 5]

# 插入元素:在指定位置插入
my_list = [0, 1, 2, 3, 4, 5]
my_list[2:2] = [20, 30]  # 在索引 2 的位置插入
print(my_list)  # 输出: [0, 1, 20, 30, 2, 3, 4, 5]

7.2 字符串和元组不支持切片赋值

# 字符串不支持切片赋值(字符串是不可变的)
text = "Hello"
# text[0:2] = "Hi"  # 这会报错:TypeError: 'str' object does not support item assignment

# 元组不支持切片赋值(元组是不可变的)
my_tuple = (1, 2, 3, 4, 5)
# my_tuple[0:2] = (10, 20)  # 这会报错:TypeError: 'tuple' object does not support item assignment

8. 切片的边界情况

8.1 索引超出范围

切片操作不会因为索引超出范围而报错,会自动调整到有效范围。

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

# 索引超出范围,自动调整
print(my_list[1:100])   # 输出: [1, 2, 3, 4](自动调整到列表末尾)
print(my_list[-100:3])  # 输出: [0, 1, 2](自动调整到列表开头)
print(my_list[-100:100]) # 输出: [0, 1, 2, 3, 4](整个列表)

# 空切片
print(my_list[10:20])   # 输出: [](空列表,因为起始索引超出范围)

8.2 start >= stop 的情况

start >= stop 且步长为正数时,返回空序列。

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

print(my_list[3:1])     # 输出: [](空列表)
print(my_list[5:2])     # 输出: [](空列表)
print(my_list[0:0])     # 输出: [](空列表)

# 但是使用负步长时,start 必须大于 stop
print(my_list[3:1:-1])  # 输出: [3, 2](正常,因为步长为负数)

8.3 空序列的切片

对空序列进行切片操作,返回空序列。

empty_list = []
print(empty_list[:])      # 输出: []
print(empty_list[0:5])    # 输出: []
print(empty_list[::-1])   # 输出: []

empty_string = ""
print(empty_string[:])     # 输出: ""
print(empty_string[0:5])  # 输出: ""
print(empty_string[::-1]) # 输出: ""

8.4 步长为 0 的情况

步长不能为 0,否则会报错。

my_list = [0, 1, 2, 3, 4]
# print(my_list[::0])  # 这会报错:ValueError: slice step cannot be zero

9. 切片的高级技巧

9.1 使用变量作为切片参数

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

# 使用变量
start = 2
stop = 7
step = 2

result = my_list[start:stop:step]
print(result)  # 输出: [2, 4, 6]

# 动态计算切片范围
def get_middle_half(lst):
    """获取列表中间一半的元素"""
    start = len(lst) // 4
    stop = len(lst) - len(lst) // 4
    return lst[start:stop]

my_list = list(range(20))
print(get_middle_half(my_list))  # 输出: [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

9.2 使用 slice 对象

可以使用 slice() 函数创建切片对象,然后用于切片操作。

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

# 创建切片对象
s = slice(2, 7, 2)
result = my_list[s]
print(result)  # 输出: [2, 4, 6]

# slice 对象的参数
s1 = slice(2, 7)      # start=2, stop=7, step=None
s2 = slice(2, 7, 2)   # start=2, stop=7, step=2
s3 = slice(None, 5)   # start=None, stop=5, step=None(相当于 [:5])
s4 = slice(5, None)   # start=5, stop=None, step=None(相当于 [5:])
s5 = slice(None, None, -1)  # 反转(相当于 [::-1])

print(my_list[s1])  # 输出: [2, 3, 4, 5, 6]
print(my_list[s2])  # 输出: [2, 4, 6]
print(my_list[s3])  # 输出: [0, 1, 2, 3, 4]
print(my_list[s4])  # 输出: [5, 6, 7, 8, 9]
print(my_list[s5])  # 输出: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

# slice 对象的属性
s = slice(2, 7, 2)
print(s.start)  # 输出: 2
print(s.stop)   # 输出: 7
print(s.step)   # 输出: 2

9.3 多维切片的限制

Python 的标准序列类型(列表、元组、字符串)不支持多维切片。但 NumPy 数组支持多维切片。

# 标准列表不支持多维切片
my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# print(my_list[0:2, 1:3])  # 这会报错

# 需要先切片行,再切片列
rows = my_list[0:2]  # 获取前两行
print(rows)  # 输出: [[1, 2, 3], [4, 5, 6]]

# 然后对每一行进行切片
result = [row[1:3] for row in rows]
print(result)  # 输出: [[2, 3], [5, 6]]

# NumPy 数组支持多维切片(需要安装 numpy)
# import numpy as np
# arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# print(arr[0:2, 1:3])  # 输出: [[2, 3], [5, 6]]

9.4 切片的性能考虑

# 切片操作会创建新的序列,对于大列表可能消耗内存
large_list = list(range(1000000))

# 这会创建一个新的列表,占用内存
sliced = large_list[:100]  # 只取前 100 个元素

# 如果只需要遍历,可以使用迭代器
from itertools import islice
sliced_iter = islice(large_list, 100)  # 返回迭代器,不创建新列表
for item in sliced_iter:
    pass  # 处理元素

10. 常见错误和注意事项

10.1 混淆索引和切片

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

# 索引:返回单个元素
print(my_list[2])      # 输出: 2(整数)

# 切片:返回序列
print(my_list[2:3])    # 输出: [2](列表,包含一个元素)

# 注意区别
print(type(my_list[2]))    # 输出: <class 'int'>
print(type(my_list[2:3]))  # 输出: <class 'list'>

10.2 忘记结束索引是不包含的

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

# 错误理解:以为 [2:5] 包含索引 5
# 正确理解:[2:5] 包含索引 2, 3, 4,不包含索引 5
print(my_list[2:5])  # 输出: [2, 3, 4],不包含索引 5 的元素

# 如果要包含索引 5,需要使用 [2:6]
print(my_list[2:6])  # 输出: [2, 3, 4, 5]

10.3 负步长时的索引顺序

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

# 使用负步长时,start 必须大于 stop
print(my_list[5:2:-1])  # 输出: [5, 4, 3](正确)
print(my_list[2:5:-1])  # 输出: [](错误,返回空列表)

10.4 切片不会修改原序列(字符串和元组)

# 字符串和元组是不可变的,切片不会修改原对象
text = "Hello"
new_text = text[::-1]
print(text)      # 输出: "Hello"(原字符串不变)
print(new_text)  # 输出: "ollH"(新字符串)

# 列表切片也不会修改原列表(除非使用切片赋值)
my_list = [0, 1, 2, 3, 4]
sliced = my_list[1:4]
sliced[0] = 999
print(sliced)    # 输出: [999, 2, 3](切片副本被修改)
print(my_list)   # 输出: [0, 1, 2, 3, 4](原列表不变)

10.5 浅拷贝的问题

# 列表的切片是浅拷贝
original = [[1, 2], [3, 4]]
shallow_copy = original[:]

# 修改浅拷贝中的嵌套列表会影响原列表
shallow_copy[0][0] = 999
print(original)      # 输出: [[999, 2], [3, 4]]
print(shallow_copy)  # 输出: [[999, 2], [3, 4]]

# 如果需要深拷贝,使用 copy.deepcopy()
import copy
original = [[1, 2], [3, 4]]
deep_copy = copy.deepcopy(original)
deep_copy[0][0] = 999
print(original)   # 输出: [[1, 2], [3, 4]](原列表不变)
print(deep_copy)   # 输出: [[999, 2], [3, 4]]

11. 实战练习

11.1 练习 1:提取字符串的不同部分

# 任务:从字符串 "2024-01-15 10:30:45" 中提取年、月、日、时、分、秒
datetime_str = "2024-01-15 10:30:45"

year = datetime_str[:4]        # "2024"
month = datetime_str[5:7]       # "01"
day = datetime_str[8:10]       # "15"
hour = datetime_str[11:13]      # "10"
minute = datetime_str[14:16]   # "30"
second = datetime_str[17:]     # "45"

print(f"年: {year}, 月: {month}, 日: {day}")
print(f"时: {hour}, 分: {minute}, 秒: {second}")

11.2 练习 2:反转字符串中的单词

# 任务:反转字符串 "Hello World Python" 中的每个单词
text = "Hello World Python"
words = text.split()  # ['Hello', 'World', 'Python']
reversed_words = [word[::-1] for word in words]
result = ' '.join(reversed_words)
print(result)  # 输出: "olleH dlroW nohtyP"

11.3 练习 3:提取列表的特定模式

# 任务:从列表中提取所有偶数索引的元素,然后反转
my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 提取偶数索引的元素
even_indices = my_list[::2]  # [0, 2, 4, 6, 8]

# 反转
reversed_even = even_indices[::-1]  # [8, 6, 4, 2, 0]

print(reversed_even)  # 输出: [8, 6, 4, 2, 0]

11.4 练习 4:实现一个简单的分页功能

# 任务:实现一个函数,将列表分成指定大小的页
def paginate(items, page_size, page_num):
    """分页函数

    Args:
        items: 要分页的列表
        page_size: 每页的大小
        page_num: 页码(从 1 开始)

    Returns:
        指定页的元素列表
    """
    start = (page_num - 1) * page_size
    stop = start + page_size
    return items[start:stop]

# 测试
my_list = list(range(1, 21))  # [1, 2, 3, ..., 20]

print(paginate(my_list, 5, 1))  # 输出: [1, 2, 3, 4, 5](第 1 页)
print(paginate(my_list, 5, 2))  # 输出: [6, 7, 8, 9, 10](第 2 页)
print(paginate(my_list, 5, 3))  # 输出: [11, 12, 13, 14, 15](第 3 页)
print(paginate(my_list, 5, 4))  # 输出: [16, 17, 18, 19, 20](第 4 页)

11.5 练习 5:检查字符串是否为回文

# 任务:检查字符串是否为回文(正读和反读都一样)
def is_palindrome(text):
    """检查字符串是否为回文"""
    # 转换为小写并移除空格
    text = text.lower().replace(' ', '')
    # 使用切片反转字符串并比较
    return text == text[::-1]

# 测试
print(is_palindrome("level"))      # 输出: True
print(is_palindrome("hello"))      # 输出: False
print(is_palindrome("A man a plan a canal Panama"))  # 输出: True

11.6 练习 6:提取文件扩展名

# 任务:从文件名中提取扩展名
def get_extension(filename):
    """提取文件扩展名"""
    # 找到最后一个点的位置
    dot_index = filename.rfind('.')
    if dot_index != -1 and dot_index < len(filename) - 1:
        return filename[dot_index+1:]
    return ""

# 测试
print(get_extension("document.pdf"))      # 输出: "pdf"
print(get_extension("image.jpg"))         # 输出: "jpg"
print(get_extension("script.py"))         # 输出: "py"
print(get_extension("no_extension"))      # 输出: ""

12. 总结

12.1 切片的核心要点

  1. 基本语法sequence[start:stop:step]

    • start:起始索引(包含)
    • stop:结束索引(不包含)
    • step:步长(可选,默认为 1)
  2. 重要特性

    • 切片返回新的序列,不修改原序列
    • 结束索引是不包含的
    • 索引超出范围不会报错,会自动调整
    • 支持正索引和负索引
    • 支持正步长和负步长
  3. 常用模式

    • [:]:获取整个序列的副本
    • [:n]:获取前 n 个元素
    • [-n:]:获取后 n 个元素
    • [::-1]:反转序列
    • [::2]:每隔一个元素取一个

12.2 适用场景

  • 字符串处理:提取子字符串、反转字符串、提取文件名等
  • 列表操作:获取部分元素、反转列表、分块处理等
  • 数据处理:提取特定范围的数据、分页等
  • 算法实现:回文检查、字符串匹配等

12.3 注意事项

  1. 字符串和元组不支持切片赋值(它们是不可变的)
  2. 列表的切片是浅拷贝,嵌套结构需要注意
  3. 使用负步长时,start 必须大于 stop
  4. 切片不会因为索引超出范围而报错
  5. 结束索引是不包含的,要记住这一点

12.4 学习建议

  1. 多练习:通过实际代码练习加深理解
  2. 画图理解:对于复杂的切片,可以画出索引图帮助理解
  3. 逐步调试:使用 print() 输出中间结果,观察切片的效果
  4. 查阅文档:遇到问题时查阅 Python 官方文档

13. 附录:快速参考表

13.1 切片语法速查表

语法 说明 示例
[start:stop] 从 start 到 stop-1 [2:5] → 索引 2, 3, 4
[:stop] 从开头到 stop-1 [:5] → 前 5 个元素
[start:] 从 start 到末尾 [5:] → 从索引 5 到末尾
[:] 整个序列的副本 [:] → 完整副本
[start:stop:step] 带步长的切片 [::2] → 每隔一个元素
[::-1] 反转序列 [::-1] → 反转
[-n:] 后 n 个元素 [-3:] → 最后 3 个
[:-n] 除了后 n 个元素 [:-3] → 除了最后 3 个

13.2 常见操作速查表

操作 代码 结果
获取前 5 个元素 lst[:5] 前 5 个元素
获取后 5 个元素 lst[-5:] 后 5 个元素
获取中间元素 lst[1:-1] 去掉首尾
反转序列 lst[::-1] 反转后的序列
偶数索引元素 lst[::2] 索引 0, 2, 4, …
奇数索引元素 lst[1::2] 索引 1, 3, 5, …
获取副本 lst[:] 浅拷贝

恭喜你完成了 Python 切片的学习! 切片是 Python 中非常强大和常用的功能,掌握它会让你的代码更加简洁高效。继续练习,在实际项目中应用这些知识,你会越来越熟练!

发表评论