14. 列表:有序的项目集合
到目前为止,在本书中我们一直在处理单个数据:单个数字、字符串和布尔值。但真实程序往往需要处理相关项目的集合——一份学生姓名列表、一系列温度读数、一组商品价格,或者一串用户命令。Python 的 列表(list) 是用于存储和处理有序数据集合的基础工具。
列表是一种 序列(sequence),可以按特定顺序保存多个项目。与字符串(只能包含字符)不同,列表可以包含任意类型的数据:数字、字符串、布尔值,甚至其他列表。列表还是 可变的(mutable),这意味着你可以在创建后更改其内容——添加项目、删除项目,或修改现有项目。
在本章中,我们将探索如何创建列表、访问其元素、修改它们,并使用它们解决实际编程问题。到最后,你将理解为什么列表是 Python 最强大、使用最频繁的数据结构之一。
14.1) 创建列表并访问元素
14.1.1) 使用方括号创建列表
创建列表最常见的方法是把项目放在 方括号(square brackets) [] 中,并用逗号分隔。下面是一个简单示例:
# 学生姓名列表
students = ["Alice", "Bob", "Charlie", "Diana"]
print(students) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']注意 Python 如何显示该列表:它会显示方括号,并在每个字符串周围加上引号。这是列表的 表示形式(representation)——也就是 Python 向你展示其内部内容的方式。
列表可以包含任何类型的数据。下面是一个测试分数列表:
# 整数分数列表
scores = [85, 92, 78, 95, 88]
print(scores) # Output: [85, 92, 78, 95, 88]你甚至可以在同一个列表中混合不同类型,不过在实际中较少见:
# 混合类型列表(较少见但有效)
mixed_data = ["Alice", 25, True, 3.14]
print(mixed_data) # Output: ['Alice', 25, True, 3.14]空列表(empty list) 不包含任何项目,只用方括号创建即可:
# 空列表
empty = []
print(empty) # Output: []
print(len(empty)) # Output: 0我们在字符串中用过的 len() 函数,同样适用于列表——它返回列表中的项目数量。
14.1.2) 理解列表的顺序与位置
列表会保持你添加项目时的 顺序(order)。你放进去的第一个项目会一直是第一个,第二个一直是第二个,依此类推。这种顺序很关键,因为它让你可以通过 位置(position)(也称为 索引(index))访问特定项目。
Python 使用 从 0 开始的索引(zero-based indexing):第一个项目的位置是 0,第二个项目的位置是 1,依此类推。一开始这可能看起来不太习惯,但这是许多编程语言都采用的约定。
我们来看一下它在实践中如何工作:
students = ["Alice", "Bob", "Charlie", "Diana"]
# 访问第一个学生(索引 0)
first_student = students[0]
print(first_student) # Output: Alice
# 访问第三个学生(索引 2)
third_student = students[2]
print(third_student) # Output: Charlie注意,为了得到第三个学生,我们使用索引 2,而不是 3。这是因为计数从 0 开始。
14.1.3) 使用正索引访问元素
要访问列表元素,写出列表名,然后在方括号中写索引:list_name[index]。索引必须是有效范围内的整数(0 到 len(list) - 1)。
下面是一个处理商品价格的实用示例:
# 以美元表示的商品价格
prices = [19.99, 24.50, 15.75, 32.00, 8.99]
# 访问特定价格
first_price = prices[0]
last_index = len(prices) - 1 # 计算最后一个有效索引
last_price = prices[last_index]
print(f"First product costs: ${first_price}") # Output: First product costs: $19.99
print(f"Last product costs: ${last_price}") # Output: Last product costs: $8.99为什么我们要用 len(prices) - 1 作为最后一个索引?因为如果一个列表有 5 个项目,那么索引是 0、1、2、3、4——最后一个有效索引总是比长度小 1。
你也可以在表达式和计算中使用索引:
scores = [85, 92, 78, 95, 88]
# 计算前三个分数的平均值
first_three_average = (scores[0] + scores[1] + scores[2]) / 3
print(f"Average of first three: {first_three_average}") # Output: Average of first three: 85.014.1.4) 负索引:从末尾计数
Python 提供了一个便利特性:负索引(negative indices) 让你可以从列表末尾访问项目。索引 -1 指向最后一个项目,-2 指向倒数第二个,依此类推。
students = ["Alice", "Bob", "Charlie", "Diana"]
# 从末尾访问
last_student = students[-1]
second_to_last = students[-2]
print(last_student) # Output: Diana
print(second_to_last) # Output: Charlie当你想要最后一个项目,但不想计算 len(list) - 1 时,这尤其有用:
prices = [19.99, 24.50, 15.75, 32.00, 8.99]
# 这两种方式等价
last_price_method1 = prices[len(prices) - 1]
last_price_method2 = prices[-1]
print(last_price_method1) # Output: 8.99
print(last_price_method2) # Output: 8.99下面展示了正索引与负索引如何映射到同一批项目:
14.1.5) 使用无效索引会发生什么
如果你尝试访问一个不存在的索引,Python 会抛出 IndexError:
students = ["Alice", "Bob", "Charlie"]
# WARNING: This list has indices 0, 1, 2 (or -3, -2, -1) - for demonstration only
# Trying to access index 3 causes an error
# PROBLEM: Index 3 doesn't exist in a 3-item list
# print(students[3]) # IndexError: list index out of range这个错误是 Python 告诉你:你请求了一个不存在的项目。
14.2) 列表索引与切片
14.2.1) 理解列表切片基础
就像我们可以对字符串进行切片(正如我们在第 5 章学到的),我们也可以对列表进行 切片(slice) 来提取其中的一部分。切片会创建一个新列表,其中包含原列表元素的一个子集。语法为 list[start:stop],其中 start 是切片开始的位置(包含),stop 是切片结束的位置(不包含)。
numbers = [10, 20, 30, 40, 50, 60, 70]
# 获取从索引 1 到(但不包含)索引 4 的元素
subset = numbers[1:4]
print(subset) # Output: [20, 30, 40]切片 [1:4] 包含索引 1、2 和 3,但在索引 4 之前停止。这个“stop 不包含”的规则与字符串切片相同。
我们来看一个学生姓名的实用示例:
students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"]
# 获取前三个学生
first_three = students[0:3]
print(first_three) # Output: ['Alice', 'Bob', 'Charlie']
# 获取索引 2 到 4 的学生
middle_group = students[2:5]
print(middle_group) # Output: ['Charlie', 'Diana', 'Eve']14.2.2) 在切片中省略 start 或 stop
你可以省略 start 索引以从开头切片,或者省略 stop 索引以切到末尾:
scores = [85, 92, 78, 95, 88, 91, 87]
# 从开头到索引 3(不包含索引 3)
first_few = scores[:3]
print(first_few) # Output: [85, 92, 78]
# 从索引 4 到末尾
last_few = scores[4:]
print(last_few) # Output: [88, 91, 87]
# 整个列表(从开头到末尾)
all_scores = scores[:]
print(all_scores) # Output: [85, 92, 78, 95, 88, 91, 87]切片 [:] 会创建整个列表的一个 副本(copy)。当你想在不修改原列表的情况下操作一个副本时,这很有用——我们会在 14.6 节进一步探讨。
14.2.3) 在切片中使用负索引
负索引在切片中的用法与访问单个元素时相同。这对于获取列表末尾的项目尤其有用:
students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"]
# 获取最后三个学生
last_three = students[-3:]
print(last_three) # Output: ['Diana', 'Eve', 'Frank']
# 获取除了最后两个学生之外的所有学生
all_but_last_two = students[:-2]
print(all_but_last_two) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']
# 获取从倒数第三个到倒数第二个(不包含倒数第一个)
middle_from_end = students[-3:-1]
print(middle_from_end) # Output: ['Diana', 'Eve']14.2.4) 带步长的切片
你可以添加第三个参数来控制 步长(step)(在项目之间跳过多少个索引)。完整语法是 list[start:stop:step]:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 从索引 0 开始每隔一个取一个数
evens = numbers[0:10:2]
print(evens) # Output: [0, 2, 4, 6, 8]
# 从索引 1 开始每隔两个取一个数(每第三个数)
every_third = numbers[1:10:3]
print(every_third) # Output: [1, 4, 7]你也可以使用 负步长(negative step) 来反转列表:
numbers = [1, 2, 3, 4, 5]
# 反转列表
reversed_numbers = numbers[::-1]
print(reversed_numbers) # Output: [5, 4, 3, 2, 1]切片 [::-1] 的意思是“从末尾开始,到开头结束,每次向后走 1”。这是 Python 中用于反转序列的一种常见习惯用法。
14.2.5) 切片永远不会导致 IndexError
与访问单个元素不同,切片非常宽容。如果你指定了超出列表范围的索引,Python 只会自动调整它们以适配:
numbers = [10, 20, 30, 40, 50]
# 请求超过实际存在的范围
extended_slice = numbers[2:100]
print(extended_slice) # Output: [30, 40, 50]
# 从末尾之外开始
empty_slice = numbers[10:20]
print(empty_slice) # Output: []这种行为很有用,因为这意味着在切片时你不必担心精确边界——Python 会优雅地处理边界情况。
14.3) 修改列表与常见列表方法
14.3.1) 列表是可变的:更改元素
与不可变的字符串不同,列表是可变的(mutable)——你可以在创建后更改其内容。你可以通过给特定索引赋新值来修改单个元素:
# 从一个价格列表开始
prices = [19.99, 24.50, 15.75, 32.00]
print(prices) # Output: [19.99, 24.5, 15.75, 32.0]
# 更新第二个价格(索引 1)
prices[1] = 22.99
print(prices) # Output: [19.99, 22.99, 15.75, 32.0]
# 使用负索引更新最后一个价格
prices[-1] = 29.99
print(prices) # Output: [19.99, 22.99, 15.75, 29.99]这种可变性很强大——它意味着你可以原地更新数据,而无需创建新列表。不过,它也意味着你需要小心避免意外更改,我们会在 14.6 节讨论这一点。
14.3.2) 使用 append() 添加元素
append() 方法会向列表的 末尾(end) 添加一个项目。这是使用最频繁的列表操作之一:
# 从一个空购物车开始
cart = []
print(cart) # Output: []
# 逐个添加商品
cart.append("Milk")
print(cart) # Output: ['Milk']
cart.append("Bread")
print(cart) # Output: ['Milk', 'Bread']
cart.append("Eggs")
print(cart) # Output: ['Milk', 'Bread', 'Eggs']注意,append() 会 原地(in place) 修改列表——它不会返回一个新列表。该方法返回 None,因此你不需要把它的结果赋值:
scores = [85, 92, 78]
result = scores.append(95)
print(scores) # Output: [85, 92, 78, 95]
print(result) # Output: None14.3.3) 使用 insert() 在指定位置插入元素
append() 总是添加到末尾,而 insert() 让你可以在任意位置添加项目。语法是 list.insert(index, item):
students = ["Alice", "Charlie", "Diana"]
print(students) # Output: ['Alice', 'Charlie', 'Diana']
# 在索引 1 插入 "Bob"(位于 Alice 和 Charlie 之间)
students.insert(1, "Bob")
print(students) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']当你在某个索引插入时,当前位置上的项目(以及其后的所有项目)会整体向右移动:
numbers = [10, 20, 30, 40]
print(numbers) # Output: [10, 20, 30, 40]
# 在索引 2 插入 25
numbers.insert(2, 25)
print(numbers) # Output: [10, 20, 25, 30, 40]你可以通过使用索引 0 在开头插入:
priorities = ["Medium", "Low"]
priorities.insert(0, "High")
print(priorities) # Output: ['High', 'Medium', 'Low']如果你指定的索引超过列表长度,insert() 会直接把项目添加到末尾(就像 append() 一样):
items = [1, 2, 3]
items.insert(100, 4)
print(items) # Output: [1, 2, 3, 4]14.3.4) 使用 remove() 删除元素
remove() 方法会从列表中删除某个特定值的 第一次出现(first occurrence):
fruits = ["apple", "banana", "cherry", "banana", "date"]
print(fruits) # Output: ['apple', 'banana', 'cherry', 'banana', 'date']
# 删除第一个 "banana"
fruits.remove("banana")
print(fruits) # Output: ['apple', 'cherry', 'banana', 'date']注意只有第一个 "banana" 被删除了——第二个仍然保留。如果你尝试删除一个不存在的值,Python 会抛出 ValueError:
numbers = [10, 20, 30]
# WARNING: Attempting to remove non-existent value - for demonstration only
# PROBLEM: 40 is not in the list
# numbers.remove(40) # ValueError: list.remove(x): x not in list为了避免这个错误,你可以在删除之前检查该项目是否存在:
cart = ["Milk", "Bread", "Eggs"]
item_to_remove = "Butter"
if item_to_remove in cart:
cart.remove(item_to_remove)
print(f"Removed {item_to_remove}")
else:
print(f"{item_to_remove} not in cart")
# Output: Butter not in cart14.3.5) 使用 pop() 删除并返回元素
pop() 方法会删除指定索引处的项目并 返回(returns) 它。如果你不指定索引,它会删除并返回最后一个项目:
scores = [85, 92, 78, 95, 88]
# 删除并获取最后一个分数
last_score = scores.pop()
print(f"Removed: {last_score}") # Output: Removed: 88
print(scores) # Output: [85, 92, 78, 95]
# 删除并获取索引 1 处的分数
second_score = scores.pop(1)
print(f"Removed: {second_score}") # Output: Removed: 92
print(scores) # Output: [85, 78, 95]当你需要一次处理列表中的一个项目时,这很有用:
tasks = ["Write code", "Test code", "Deploy code"]
while len(tasks) > 0:
current_task = tasks.pop(0) # 从开头移除
print(f"Working on: {current_task}")
# Output:
# Working on: Write code
# Working on: Test code
# Working on: Deploy code
print(tasks) # Output: []14.3.6) 使用 extend() 扩展列表
extend() 方法会把另一个列表(或任何可迭代对象)中的所有项目添加到当前列表的末尾:
primary_colors = ["red", "blue", "yellow"]
secondary_colors = ["green", "orange", "purple"]
# 将所有 secondary_colors 添加到 primary_colors
primary_colors.extend(secondary_colors)
print(primary_colors)
# Output: ['red', 'blue', 'yellow', 'green', 'orange', 'purple']这与 append() 不同,后者会把整个列表作为单个元素添加进去:
colors1 = ["red", "blue"]
colors2 = ["green", "orange"]
# 使用 append(把列表作为一个元素添加)
colors1.append(colors2)
print(colors1) # Output: ['red', 'blue', ['green', 'orange']]
# 使用 extend(逐个添加每个元素)
colors3 = ["red", "blue"]
colors3.extend(colors2)
print(colors3) # Output: ['red', 'blue', 'green', 'orange']14.3.7) 使用 sort() 和 sorted() 排序列表
Python 提供了两种方式对列表排序。sort() 方法会 原地(in place) 排序(修改原列表):
scores = [78, 95, 85, 92, 88]
scores.sort()
print(scores) # Output: [78, 85, 88, 92, 95]要按降序排序,使用 reverse 参数:
scores = [78, 95, 85, 92, 88]
scores.sort(reverse=True)
print(scores) # Output: [95, 92, 88, 85, 78]sorted() 函数(我们会在第 38 章更深入地探讨)会创建一个 新的已排序列表(new sorted list),而不会修改原列表:
original = [78, 95, 85, 92, 88]
sorted_scores = sorted(original)
print(original) # Output: [78, 95, 85, 92, 88]
print(sorted_scores) # Output: [78, 85, 88, 92, 95]排序同样适用于字符串,使用字母顺序:
names = ["Charlie", "Alice", "Diana", "Bob"]
names.sort()
print(names) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']14.3.8) 使用 reverse() 反转列表
reverse() 方法会 原地(in place) 反转列表:
numbers = [1, 2, 3, 4, 5]
numbers.reverse()
print(numbers) # Output: [5, 4, 3, 2, 1]这与按降序排序不同——reverse() 只是把当前顺序翻转,不管它原来是什么:
mixed = [3, 1, 4, 1, 5]
mixed.reverse()
print(mixed) # Output: [5, 1, 4, 1, 3]记住,你也可以使用切片来反转列表:list[::-1]。区别在于切片会创建一个新列表,而 reverse() 会修改原列表。
14.3.9) 使用 index() 和 count() 查找元素
index() 方法返回某个值第一次出现的位置:
students = ["Alice", "Bob", "Charlie", "Diana", "Bob"]
# 查找 "Charlie" 的位置
position = students.index("Charlie")
print(f"Charlie is at index {position}") # Output: Charlie is at index 2
# 查找第一个 "Bob"
bob_position = students.index("Bob")
print(f"Bob is at index {bob_position}") # Output: Bob is at index 1如果该值不存在,index() 会抛出 ValueError:
students = ["Alice", "Bob", "Charlie"]
# WARNING: Attempting to find non-existent value - for demonstration only
# PROBLEM: 'Eve' is not in the list
# position = students.index("Eve") # ValueError: 'Eve' is not in listcount() 方法返回某个值出现的次数:
numbers = [1, 2, 3, 2, 4, 2, 5]
twos = numbers.count(2)
print(f"The number 2 appears {twos} times") # Output: The number 2 appears 3 times
# 如果项目不存在,count 可以返回 0
sixes = numbers.count(6)
print(f"The number 6 appears {sixes} times") # Output: The number 6 appears 0 times14.3.10) 使用 clear() 清空所有元素
clear() 方法会移除列表中的所有项目,使其变为空列表:
cart = ["Milk", "Bread", "Eggs", "Butter"]
print(cart) # Output: ['Milk', 'Bread', 'Eggs', 'Butter']
cart.clear()
print(cart) # Output: []
print(len(cart)) # Output: 0这等价于赋值一个空列表,但 clear() 更明确地表达了意图。
14.4) 使用 del 删除列表元素
14.4.1) 使用 del 按索引删除元素
del 语句可以删除列表中指定索引处的元素:
students = ["Alice", "Bob", "Charlie", "Diana", "Eve"]
print(students) # Output: ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve']
# 删除索引 2 处的元素
del students[2]
print(students) # Output: ['Alice', 'Bob', 'Diana', 'Eve']与 pop() 不同,del 不会返回被删除的值——它只是删除。这在你想移除某个项目但不需要使用它时很有用:
scores = [85, 92, 78, 95, 88]
# 移除最低分(位于索引 2)
del scores[2]
print(scores) # Output: [85, 92, 95, 88]你也可以在 del 中使用负索引:
tasks = ["Task 1", "Task 2", "Task 3", "Task 4"]
# 删除最后一个任务
del tasks[-1]
print(tasks) # Output: ['Task 1', 'Task 2', 'Task 3']14.4.2) 使用 del 删除切片
del 语句可以一次性删除整个切片:
numbers = [10, 20, 30, 40, 50, 60, 70]
print(numbers) # Output: [10, 20, 30, 40, 50, 60, 70]
# 删除索引 2 到 4 的元素(索引 2、3、4)
del numbers[2:5]
print(numbers) # Output: [10, 20, 60, 70]这对于删除一段范围的元素尤其有用:
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 移除前三个元素
del data[:3]
print(data) # Output: [4, 5, 6, 7, 8, 9, 10]
# 移除最后两个元素
del data[-2:]
print(data) # Output: [4, 5, 6, 7, 8]你甚至可以使用带步长的切片删除隔一个元素删一个:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 删除每隔一个元素
del numbers[::2]
print(numbers) # Output: [1, 3, 5, 7, 9]14.4.3) 对比 del、remove() 和 pop()
我们来澄清什么时候使用每种删除方式:
# 用于对比的示例列表
items = ["apple", "banana", "cherry", "date", "elderberry"]
# 当你知道要删除的 VALUE 时使用 remove()
items_copy1 = items.copy()
items_copy1.remove("cherry") # Removes first "cherry"
print(items_copy1) # Output: ['apple', 'banana', 'date', 'elderberry']
# 当你知道 INDEX 且需要该值时使用 pop()
items_copy2 = items.copy()
removed_item = items_copy2.pop(2) # Removes and returns item at index 2
print(f"Removed: {removed_item}") # Output: Removed: cherry
print(items_copy2) # Output: ['apple', 'banana', 'date', 'elderberry']
# 当你知道 INDEX 但不需要该值时使用 del
items_copy3 = items.copy()
del items_copy3[2] # Just removes item at index 2
print(items_copy3) # Output: ['apple', 'banana', 'date', 'elderberry']14.5) 使用 for 循环遍历列表
14.5.1) 基本的列表遍历
列表最常见的操作之一是按顺序处理每个项目。for 循环(我们在第 12 章学到的)非常适合做这件事:
students = ["Alice", "Bob", "Charlie", "Diana"]
# 处理每个学生
for student in students:
print(f"Hello, {student}!")
# Output:
# Hello, Alice!
# Hello, Bob!
# Hello, Charlie!
# Hello, Diana!循环变量(此处为 student)会按顺序一次取列表中的一个值。你可以给这个变量起任何有意义的名字:
scores = [85, 92, 78, 95, 88]
# 计算并显示每个分数对应的等级
for score in scores:
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
else:
grade = "C"
print(f"Score {score} is a {grade}")
# Output:
# Score 85 is a B
# Score 92 is a A
# Score 78 is a C
# Score 95 is a A
# Score 88 is a B14.5.2) 处理多个列表中的对应项目
有时你需要处理存放在多个列表中的相关数据。我们会在第 38 章详细学习 zip() 函数,但下面先简单预览一下它如何帮助你处理对应项目:
# 我们会在第 38 章学习 zip(),但现在先看一个简单示例
students = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
# 处理对应的配对
for student, score in zip(students, scores):
print(f"{student} scored {score}")
# Output:
# Alice scored 85
# Bob scored 92
# Charlie scored 78zip() 函数会将多个列表的元素配对起来,当你把相关数据存放在不同列表时很有用。我们会在第 38 章深入探索这个函数以及其他迭代工具。
14.6) 复制列表并避免共享引用
14.6.1) 理解列表引用
当你把一个列表赋值给变量时,Python 并不会创建该列表的副本——它创建的是对内存中同一个列表对象的 引用(reference)。这意味着多个变量可以引用同一个列表:
original = [1, 2, 3]
reference = original # 两个变量都指向同一个列表
# 通过其中一个变量修改会影响另一个
reference.append(4)
print(original) # Output: [1, 2, 3, 4]
print(reference) # Output: [1, 2, 3, 4]如果你期望 reference 是独立副本,这种行为可能会让人意外。我们来看看为什么这很重要:
# 场景:你想跟踪购物车的变化
cart = ["Milk", "Bread"]
backup = cart # 尝试保存原始状态
# 添加更多商品
cart.append("Eggs")
cart.append("Butter")
# 查看“备份”
print(backup) # Output: ['Milk', 'Bread', 'Eggs', 'Butter']备份也变了!这是因为 backup 和 cart 是同一个列表对象的两个名字。
14.6.2) 使用切片创建独立副本
要创建真正独立的副本,请使用 [:] 切片:
original = [1, 2, 3]
copy = original[:] # 创建一个内容相同的新列表
# 修改 copy 不会影响 original
copy.append(4)
print(original) # Output: [1, 2, 3]
print(copy) # Output: [1, 2, 3, 4]现在我们来修复购物车示例:
cart = ["Milk", "Bread"]
backup = cart[:] # 创建独立副本
# 向 cart 添加更多商品
cart.append("Eggs")
cart.append("Butter")
# backup 保持不变
print(cart) # Output: ['Milk', 'Bread', 'Eggs', 'Butter']
print(backup) # Output: ['Milk', 'Bread']14.6.3) 使用 copy() 方法创建副本
列表也有一个 copy() 方法,作用与 [:] 相同:
original = [10, 20, 30]
copy = original.copy()
copy.append(40)
print(original) # Output: [10, 20, 30]
print(copy) # Output: [10, 20, 30, 40][:] 和 copy() 都会创建 浅拷贝(shallow copies),我们接下来会讨论这一点。
14.6.4) 浅拷贝的限制
[:] 和 copy() 都会创建 浅拷贝(shallow copies)。这意味着它们会复制列表结构,但如果列表包含其他可变对象(比如其他列表),这些内部对象仍然是共享的:
# 一个包含列表的列表
original = [[1, 2], [3, 4], [5, 6]]
copy = original[:]
# 修改外层列表结构是相互独立的
copy.append([7, 8])
print(original) # Output: [[1, 2], [3, 4], [5, 6]]
print(copy) # Output: [[1, 2], [3, 4], [5, 6], [7, 8]]
# 但修改内层列表会同时影响两者!
copy[0].append(99)
print(original) # Output: [[1, 2, 99], [3, 4], [5, 6]]
print(copy) # Output: [[1, 2, 99], [3, 4], [5, 6], [7, 8]]为什么会这样?因为浅拷贝会创建一个新的外层列表,但内部列表仍然是共享引用:
对于嵌套结构,你需要 深拷贝(deep copy),我们会在后续章节学习 copy 模块时再讲。现在请记住:浅拷贝对于由不可变项目(数字、字符串、元组)组成的列表完全没问题,但对于嵌套的可变结构则需要格外小心。
14.6.5) 共享引用何时有用
有时你 确实想要 多个变量引用同一个列表。当你需要从代码的不同部分修改同一个列表时,这会很有用:
# 一个原地修改列表的函数
def add_bonus_points(scores, bonus):
for i in range(len(scores)):
scores[i] = scores[i] + bonus
# 原列表被修改
student_scores = [85, 92, 78]
add_bonus_points(student_scores, 5)
print(student_scores) # Output: [90, 97, 83]这是因为函数接收到的是原列表的引用,而不是副本。我们会在第五部分详细学习函数时进一步探讨这一点。
14.7) 在遍历列表时使用 enumerate()
14.7.1) 同时需要索引和值的需求
有时在遍历列表时,你既需要索引也需要值。一种做法是使用 range(len(list)):
students = ["Alice", "Bob", "Charlie", "Diana"]
for i in range(len(students)):
print(f"Student {i}: {students[i]}")
# Output:
# Student 0: Alice
# Student 1: Bob
# Student 2: Charlie
# Student 3: Diana这样可行,但不够优雅。你必须用 students[i] 访问每个值,这不如直接遍历值那样可读。
14.7.2) 使用 enumerate() 让代码更简洁
enumerate() 函数提供了更好的解决方案。它会为每个项目返回索引和值:
students = ["Alice", "Bob", "Charlie", "Diana"]
for index, student in enumerate(students):
print(f"Student {index}: {student}")
# Output:
# Student 0: Alice
# Student 1: Bob
# Student 2: Charlie
# Student 3: Diana语法 for index, value in enumerate(list) 会解包 enumerate() 生成的每个二元组。这比使用 range(len()) 可读性强得多。
14.7.3) 让 enumerate() 从不同数字开始
默认情况下,enumerate() 从 0 开始计数。你可以使用 start 参数指定不同的起始数字:
students = ["Alice", "Bob", "Charlie", "Diana"]
# 从 1 而不是 0 开始计数
for position, student in enumerate(students, start=1):
print(f"Position {position}: {student}")
# Output:
# Position 1: Alice
# Position 2: Bob
# Position 3: Charlie
# Position 4: Diana当你希望显示更符合人类习惯的编号(从 1 开始),而不是更符合程序员习惯的索引(从 0 开始)时,这很有用。
使用 enumerate() 的实用示例
下面是一个显示带编号菜单的实用示例:
menu_items = ["New Game", "Load Game", "Settings", "Quit"]
print("Main Menu:")
for number, item in enumerate(menu_items, start=1):
print(f"{number}. {item}")
# Output:
# Main Menu:
# 1. New Game
# 2. Load Game
# 3. Settings
# 4. Quit14.7.4) 使用 enumerate() 修改列表
当你需要根据位置来修改列表元素时,可以使用 enumerate():
# 给分数加上基于位置的奖励分
scores = [85, 92, 78, 95, 88]
for index, score in enumerate(scores):
# 第一个学生加 5 分,第二个加 4 分,依此类推
bonus = 5 - index
if bonus > 0:
scores[index] = score + bonus
print(scores) # Output: [90, 96, 81, 97, 89]14.8) 常见列表模式:搜索、过滤与聚合数据
14.8.1) 在列表中搜索项目
最常见的任务之一是检查列表是否包含某个特定项目。in 运算符(我们在第 7 章学到的)让这件事变得简单:
students = ["Alice", "Bob", "Charlie", "Diana"]
# 检查某个学生是否在列表中
if "Charlie" in students:
print("Charlie is enrolled") # Output: Charlie is enrolled
if "Eve" not in students:
print("Eve is not enrolled") # Output: Eve is not enrolled要找到某个项目的位置,使用 index() 方法(在 14.3.9 节讲过),但要记得先检查该项目是否存在:
scores = [85, 92, 78, 95, 88]
target_score = 95
if target_score in scores:
position = scores.index(target_score)
print(f"Score {target_score} found at index {position}")
# Output: Score 95 found at index 3
else:
print(f"Score {target_score} not found")14.8.2) 查找最大值和最小值
Python 内置的 max() 和 min() 函数适用于列表:
scores = [85, 92, 78, 95, 88, 91, 76]
highest_score = max(scores)
lowest_score = min(scores)
print(f"Highest score: {highest_score}") # Output: Highest score: 95
print(f"Lowest score: {lowest_score}") # Output: Lowest score: 7614.8.3) 计算聚合值:总和、平均值与数量
计算总数与平均值是基础的列表操作:
scores = [85, 92, 78, 95, 88, 91, 76, 89]
# 计算总和与平均值
total = sum(scores)
count = len(scores)
average = total / count
print(f"Total: {total}") # Output: Total: 694
print(f"Count: {count}") # Output: Count: 8
print(f"Average: {average:.2f}") # Output: Average: 86.75下面是一个计算购物车总价的实用示例:
cart_items = ["Milk", "Bread", "Eggs", "Butter", "Cheese"]
prices = [3.99, 2.49, 4.99, 5.49, 6.99]
# 计算总成本
total_cost = sum(prices)
item_count = len(cart_items)
print(f"Items in cart: {item_count}")
print(f"Total cost: ${total_cost:.2f}")
# Output:
# Items in cart: 5
# Total cost: $23.9514.9) 列表的可变性与条件中的真值性
14.9.1) 在实践中理解列表可变性
在本章中我们已经多次看到列表是 可变的(mutable)——它们可以在创建后被更改。正是这种可变性使列表在存储和操作数据集合时非常强大。让我们用一个综合示例来巩固理解:
# 从一个空任务列表开始
tasks = []
print(f"Initial tasks: {tasks}") # Output: Initial tasks: []
# 添加任务
tasks.append("Write code")
tasks.append("Test code")
tasks.append("Deploy code")
print(f"After adding: {tasks}")
# Output: After adding: ['Write code', 'Test code', 'Deploy code']
# 在开头插入一个紧急任务
tasks.insert(0, "Review requirements")
print(f"After inserting: {tasks}")
# Output: After inserting: ['Review requirements', 'Write code', 'Test code', 'Deploy code']
# 完成并移除第一个任务
completed = tasks.pop(0)
print(f"Completed: {completed}") # Output: Completed: Review requirements
print(f"Remaining: {tasks}")
# Output: Remaining: ['Write code', 'Test code', 'Deploy code']
# 修改一个任务
tasks[1] = "Test code thoroughly"
print(f"After modifying: {tasks}")
# Output: After modifying: ['Write code', 'Test code thoroughly', 'Deploy code']14.9.2) 可变 vs 不可变:列表 vs 字符串
理解可变列表与不可变字符串之间的区别很重要。对于字符串,操作会创建新字符串,而不是修改原字符串:
# 字符串是不可变的
text = "hello"
text.upper() # 创建一个新字符串,不会改变原字符串
print(text) # Output: hello (unchanged)
# 要“更改”字符串,你必须重新赋值
text = text.upper()
print(text) # Output: HELLO
# 列表是可变的
numbers = [1, 2, 3]
numbers.append(4) # 原地修改列表
print(numbers) # Output: [1, 2, 3, 4] (changed)这种差异会影响你如何使用这些类型:
# 字符串操作需要重新赋值
name = "alice"
name = name.capitalize() # 必须重新赋值才能看到变化
print(name) # Output: Alice
# 列表操作会原地修改
scores = [85, 92, 78]
scores.append(95) # 不需要重新赋值
print(scores) # Output: [85, 92, 78, 95]14.9.3) 在布尔上下文中使用列表
列表具有 真值性(truthiness):空列表会被视为 False,任何非空列表会被视为 True。这在条件语句中很有用:
# 空列表为假值
empty_cart = []
if empty_cart:
print("Cart has items")
else:
print("Cart is empty") # Output: Cart is empty
# 非空列表为真值
cart_with_items = ["Milk", "Bread"]
if cart_with_items:
print("Cart has items") # Output: Cart has items这个模式通常用于在处理之前检查列表是否有元素:
students = ["Alice", "Bob", "Charlie"]
if students:
print(f"We have {len(students)} students")
for student in students:
print(f" - {student}")
else:
print("No students enrolled")
# Output:
# We have 3 students
# - Alice
# - Bob
# - Charlie14.9.4) 实用模式:一直处理直到为空
列表的真值性支持一种很有用的模式:一直处理项目直到列表为空:
# 处理任务直到全部完成
tasks = ["Task 1", "Task 2", "Task 3"]
while tasks: # 当列表不为空时继续
current_task = tasks.pop(0)
print(f"Processing: {current_task}")
print("All tasks completed!")
# Output:
# Processing: Task 1
# Processing: Task 2
# Processing: Task 3
# All tasks completed!14.9.5) 检查空列表:显式 vs 隐式
检查列表是否为空有两种方式:
items = []
# 隐式检查(Pythonic)
if not items:
print("List is empty") # Output: List is empty
# 显式检查(同样有效)
if len(items) == 0:
print("List is empty") # Output: List is empty隐式检查(if not items:)通常在 Python 中更受推荐,因为它更简洁,并且适用于任何集合类型。不过,两种方式都正确,你在真实代码中会看到两者。
14.9.6) 可变性与函数行为
当你把列表传给函数时(我们会在第五部分详细探讨),函数接收到的是对同一个列表对象的引用。这意味着函数可以修改原列表:
def add_item(shopping_list, item):
shopping_list.append(item)
print(f"Added {item}")
# 原列表会被修改
cart = ["Milk", "Bread"]
print(f"Before: {cart}") # Output: Before: ['Milk', 'Bread']
add_item(cart, "Eggs") # Output: Added Eggs
print(f"After: {cart}") # Output: After: ['Milk', 'Bread', 'Eggs']这种行为与字符串和数字等不可变类型不同,后者的原始值无法被函数更改。理解这一差异对于编写正确的程序至关重要。
列表是 Python 最基础、最通用的数据结构之一。它们提供了一个有序、可变的集合,能够按需增长和缩减,使其非常适合存储和处理相关数据的序列。你已经学会了如何创建列表,通过索引与切片访问元素,使用多种方法进行修改,高效地遍历它们,并理解它们的可变特性。
我们探索过的模式——搜索、过滤、聚合与转换数据——构成了在 Python 中处理集合的基础。随着你继续学习,你将发现更多强大的列表用法,包括列表推导式(第 35 章)和高级迭代技术(第 36–37 章)。不过,你在本章掌握的基础知识将会在你的 Python 编程旅程中一直为你提供帮助。