12. 使用 for 循环与 range() 进行迭代
在第 11 章中,我们学习了 while 循环,它会在条件保持为真时重复执行操作。虽然 while 循环功能强大且灵活,但它要求我们手动管理循环计数器并更新条件。Python 还提供了另一种循环——for 循环——它擅长处理一个不同但极其常见的任务:一次处理数据集合中的每一项。
for 循环是 Python 中最常用的特性之一。无论你是在处理文件中的每一行、从一串数字中计算统计值,还是检查字符串中的每个字符,for 循环都提供了一种干净、可读的方式来遍历数据序列。在本章中,我们将探索 for 循环的工作方式,如何使用 Python 的 range() 函数进行计数操作,以及如何使用 break、continue 和较少为人所知的 else 子句来控制循环执行。
12.1) for 循环与迭代概念
12.1.1) 什么是 for 循环?
Python 中的 for 循环(for loop) 会针对序列中的每个元素重复执行一次代码块。这个序列可以是字符串、列表(list)(我们将在第 14 章中详细学习),或任何其他项目集合。它与 while 循环的关键区别在于:for 循环会自动处理迭代(iteration)——你不需要手动更新计数器或检查条件。
下面是 for 循环的基本结构:
for variable in sequence:
# 针对每个元素要执行的代码块
# variable 保存当前元素for 循环的工作方式如下:
- Python 从序列中取出第一个元素,并将其赋值给变量
- 执行代码块(位于
for语句下方并缩进) - Python 取出下一个元素,赋值给变量,然后再次执行代码块
- 持续进行,直到序列中的所有元素都被处理完
我们来看一个针对字符串的简单示例:
# 打印名字中的每个字符
name = "Alice"
for character in name:
print(character)输出:
A
l
i
c
e在这个示例中,字符串 "Alice" 是序列。循环一次处理一个字符。在第一次迭代中,character 保存 'A',第二次迭代保存 'l',以此类推。变量名 character 是我们自己选的——我们也可以把它命名为 letter、char,甚至只是 c。请选择能让代码意图清晰的名称。
循环变量命名约定:编写 for 循环时,选择能描述每个元素所代表含义的变量名。当某个值对于理解代码很重要时,使用具有描述性的名称,例如 character、student 或 score。当你只是为了统计迭代次数而使用简单的数字计数器时,用 i。当你完全不需要这个值时,用 _(下划线)——这是 Python 的一种约定,表示“我不关心这个值”。
下面是一个实际示例,用于统计单词中的元音:
# 统计单词中的元音字母
word = "Python"
vowel_count = 0
for letter in word:
if letter in "aeiouAEIOU":
vowel_count += 1
print(f"Found vowel: {letter}")
print(f"Total vowels: {vowel_count}")输出:
Found vowel: o
Total vowels: 1循环会检查 "Python" 中的每个字母。当它找到元音(使用我们在第 7 章学过的 in 运算符)时,就将计数器加一,并打印一条消息。这种模式——在循环前初始化计数器,然后在循环内更新它——在编程中非常常见。
下面是另一个示例,通过构建结果来演示累积(accumulation):
# 统计字符串中的大写字母数量
text = "Python Programming"
uppercase_count = 0
for char in text:
if char.isupper():
uppercase_count += 1
print(f"Found {uppercase_count} uppercase letters in '{text}'")输出:
Found 2 uppercase letters in 'Python Programming'这个示例展示了累积模式:我们在循环前把计数器初始化为 0,然后检查每个字符,并在发现大写字母时递增计数器。循环完成后,我们使用累积得到的结果。这种模式在编程中无处不在——统计满足某些条件的项目、对数值求和,或逐步构建结果。
12.1.2) 理解迭代
迭代(iteration) 是指对集合中的每个元素重复执行某个操作的过程。当我们说某个对象是 可迭代的(iterable),意味着 Python 可以在 for 循环中一次处理它的一个元素。字符串是可迭代的——我们可以遍历其中的字符。正如我们马上会看到的,许多其他 Python 类型也同样是可迭代的。
for 循环中的变量(例如我们示例里的 character 或 letter)被称为 循环变量(loop variable)。这个变量由 for 循环自动创建,并且只在循环的作用域内存在。每次进入循环时,Python 都会将序列中的下一个元素赋值给该变量。
下面的示例演示了循环变量是如何变化的:
# 演示循环变量如何更新
colors = "RGB" # Red, Green, Blue
for color_code in colors:
print(f"Processing color code: {color_code}")
print(f" This is iteration for '{color_code}'")输出:
Processing color code: R
This is iteration for 'R'
Processing color code: G
This is iteration for 'G'
Processing color code: B
This is iteration for 'B'每次进入循环时,color_code 都保存字符串中的不同字符。循环会自动移动到下一个字符——我们不需要编写任何代码来推进序列。
12.1.3) 用于迭代时的 for 循环 vs while 循环
我们也可以用 while 循环实现同样的逐字符处理,但这需要更多代码并且需要手动管理:
# 使用 while 循环遍历字符串(更复杂)
name = "Alice"
index = 0
while index < len(name):
character = name[index]
print(character)
index += 1 # 必须手动更新索引它会产生与前面 for 循环示例相同的输出,但注意我们不得不做这些事情:
- 创建并初始化一个索引变量
- 在每次迭代时检查条件
index < len(name) - 通过
name[index]手动取出字符 - 记得递增索引(忘记这一点会导致无限循环!)
而 for 循环会自动处理这一切:
# 使用 for 循环遍历字符串(更简单)
name = "Alice"
for character in name:
print(character)这样写更干净,也更不容易出错。当你想处理集合中的每一项时,for 循环是自然的选择。当你需要对迭代逻辑有更多控制,或者你事先不知道需要重复多少次时,使用 while 循环。
12.2) 使用 range() 进行计数循环
12.2.1) 介绍 range() 函数
虽然 for 循环擅长处理已有的序列,但我们经常需要将某个操作重复执行特定次数——例如打印五次消息,或计算前十个平方数。Python 的 range() 函数会生成一个数字序列,我们可以用 for 循环对它进行迭代。
range() 的最简单形式只接收一个参数——要生成的整数个数,从 0 开始:
# 打印从 0 到 4 的数字
for number in range(5):
print(number)输出:
0
1
2
3
4⚠️ 常见错误:初学者常常以为 range(5) 会产生 1, 2, 3, 4, 5,但它实际产生的是 0, 1, 2, 3, 4。记住:range(n) 从 0 开始,到 n 之前停止。这种“在停止值之前停下”的行为与 Python 的切片(slice)一致,我们在第 5 章学过。该范围默认从 0 开始,并一直到(但不包含)停止值。
这个模式非常适合将某个操作重复执行固定次数:
# 打印五次欢迎语
for i in range(5):
print(f"Welcome! (iteration {i})")输出:
Welcome! (iteration 0)
Welcome! (iteration 1)
Welcome! (iteration 2)
Welcome! (iteration 3)
Welcome! (iteration 4)变量 i 是循环计数器的常见名称(是 “index” 或 “iteration” 的缩写),不过你也可以使用任何合法的变量名。当循环变量的值对你的逻辑并不重要时,i 是一种惯用选择。
12.2.2) 指定起始值和停止值
你可以为 range() 提供两个参数,以同时指定从哪里开始以及在哪里停止:
# 打印从 1 到 5 的数字
for number in range(1, 6):
print(number)输出:
1
2
3
4
5这里,range(1, 6) 从 1 开始,到 6 之前停止,得到 1 到 5 的数字。当你需要从非零值开始计数时,这很有用。
我们用它来计算一个简单的乘法表:
# 打印 7 的乘法表(1 到 10)
multiplier = 7
for number in range(1, 11):
result = multiplier * number
print(f"{multiplier} × {number} = {result}")输出:
7 × 1 = 7
7 × 2 = 14
7 × 3 = 21
7 × 4 = 28
7 × 5 = 35
7 × 6 = 42
7 × 7 = 49
7 × 8 = 56
7 × 9 = 63
7 × 10 = 70循环从 1 运行到 10,将每个数字乘以 7。这展示了 range() 如何让你轻松地在特定数值序列上执行计算。
12.2.3) 使用步长值
range() 函数还接受第三个可选参数:步长(step) 值,它决定数字之间每次增加(或减少)多少:
# 打印从 0 到 10 的偶数
for number in range(0, 11, 2):
print(number)输出:
0
2
4
6
8
10使用 range(0, 11, 2) 时,我们从 0 开始,到 11 之前停止,并且每次增加 2。这样就得到从 0 到 10 的所有偶数。
你也可以使用负步长来倒数:
# 从 10 倒数到 1
for number in range(10, 0, -1):
print(number)
print("Liftoff!")输出:
10
9
8
7
6
5
4
3
2
1
Liftoff!这里,range(10, 0, -1) 从 10 开始,到 0 之前停止,并且每次减少 1。负步长让 range 反向计数。
我们来看一个实际示例:计算从 1 到 100 的所有奇数之和:
# 求 1 到 100 的所有奇数之和
total = 0
for number in range(1, 101, 2): # 从 1 开始,步长为 2
total += number
print(f"Sum of odd numbers from 1 to 100: {total}")输出:
Sum of odd numbers from 1 to 100: 2500通过使用 range(1, 101, 2),我们只生成奇数(1, 3, 5, ..., 99),避免了在循环内部检查每个数字的奇偶性。这让代码更高效,并且意图更清晰。
12.2.4) range() 实际返回什么
range() 函数不会在内存中创建一个数字列表——它会创建一个 range 对象(range object),按需生成数字。这在内存方面更高效,尤其是对很大的范围:
# range() 返回的是 range 对象,而不是列表
numbers = range(1000000)
print(type(numbers)) # Output: <class 'range'>
print(numbers) # Output: range(0, 1000000)输出:
<class 'range'>
range(0, 1000000)尽管这个 range 表示一百万个数字,但它占用的内存很少,因为 Python 并不会在你迭代之前真正创建这百万个数字。每个数字只会在循环需要时才生成。
如果你确实需要一个实际的数字列表,你可以将 range 转换为 list:
# 将 range 转换为列表
small_numbers = list(range(5))
print(small_numbers) # Output: [0, 1, 2, 3, 4]输出:
[0, 1, 2, 3, 4]我们会在第 14 章学习更多关于列表以及 list() 函数的内容。现在只需要知道:range() 无需任何转换就能很好地与 for 循环配合使用。
12.3) 遍历字符串与其他序列
12.3.1) 逐字符遍历字符串
我们已经看到了多个遍历字符串的示例。这是 for 循环最常见的用途之一,因为字符串是字符序列,我们经常需要逐个检查或处理每个字符。
下面是一个示例:通过检查是否包含至少一个数字来验证密码:
# 检查密码是否至少包含一个数字
password = "secure123"
has_digit = False
for character in password:
if character.isdigit():
has_digit = True
print(f"Found digit: {character}")
if has_digit:
print("Password contains at least one digit ✓")
else:
print("Password must contain at least one digit ✗")输出:
Found digit: 1
Found digit: 2
Found digit: 3
Password contains at least one digit ✓循环使用字符串方法 .isdigit()(我们在第 5 章学过)检查每个字符。当它找到数字时,就将 has_digit 设为 True。循环结束后,我们检查这个标志(flag),以确定是否找到了任何数字。
下面是另一个实际示例:统计不同类型的字符:
# 分析字符串中的字符类型
text = "Hello, World! 123"
letters = 0
digits = 0
spaces = 0
other = 0
for char in text:
if char.isalpha():
letters += 1
elif char.isdigit():
digits += 1
elif char.isspace():
spaces += 1
else:
other += 1
print(f"Letters: {letters}")
print(f"Digits: {digits}")
print(f"Spaces: {spaces}")
print(f"Other: {other}")输出:
Letters: 10
Digits: 3
Spaces: 2
Other: 2这个循环使用我们在第 5 章学过的字符串方法对每个字符进行分类。if-elif-else 链(来自第 8 章)确保每个字符恰好只被计入一个类别。
12.3.2) 使用索引处理字符串
有时你既需要字符,也需要它在字符串中的位置。你可以使用 range(len(string)) 来遍历索引:
# 查找特定字符出现的位置
text = "Mississippi"
search_char = "s"
print(f"Looking for '{search_char}' in '{text}':")
for index in range(len(text)):
if text[index] == search_char:
print(f" Found at index {index}")输出:
Looking for 's' in 'Mississippi':
Found at index 2
Found at index 3
Found at index 5
Found at index 6循环会遍历从 0 到 len(text) - 1 的索引。对每个索引,我们检查该位置上的字符是否与要查找的字符相同。这种方法在你需要知道某个东西出现在哪里,而不仅仅是出现了与否时很有用。
12.3.3) 遍历其他序列类型
for 循环适用于 Python 中任何可迭代序列。虽然本章重点关注字符串,但你会在后续章节学习其他序列类型。例如,第 14 章会教你列表(list),它是有序集合,可以保存任意类型的多个值。无论你在遍历什么类型的序列,for 循环的语法都保持不变——循环会自动处理从序列中获取每一项。
12.4) 在 for 循环中使用 break 与 continue
12.4.1) for 循环中的 break 语句
与 while 循环一样(第 11 章),break 语句会立即退出 for 循环,跳过任何剩余的迭代。当你已经找到想要的内容、不需要继续搜索时,这非常有用。
下面是一个搜索特定字符的示例:
# 查找字符串中的第一个元音字母
text = "Python"
found_vowel = False
for char in text:
if char.lower() in "aeiou":
print(f"First vowel found: {char}")
found_vowel = True
break # 找到第一个元音后停止搜索
if not found_vowel:
print("No vowels found")输出:
First vowel found: o如果没有 break,循环即使在找到第一个元音后也会继续检查剩余字符。break 语句通过在任务完成时立即停止,使代码更高效。
下面是一个实际示例:通过检查非法字符来验证用户输入:
# 检查用户名是否只包含允许的字符
username = "alice_123"
allowed = "abcdefghijklmnopqrstuvwxyz0123456789_"
is_valid = True
for char in username:
if char.lower() not in allowed:
print(f"Invalid character found: '{char}'")
is_valid = False
break # 无需继续检查
if is_valid:
print(f"Username '{username}' is valid ✓")
else:
print(f"Username '{username}' is invalid ✗")输出:
Username 'alice_123' is valid ✓循环将每个字符与允许的字符集进行比对。如果发现非法字符,它会报告问题并立即跳出循环——一旦我们知道它无效,就没有必要再检查剩余的用户名了。
12.4.2) for 循环中的 continue 语句
continue 语句会跳过当前迭代的剩余部分,并移动到序列中的下一个元素。当你想跳过某些元素但不想完全退出循环时,这非常有用。
下面是一个只处理某些字符的示例:
# 只打印字符串中的辅音字母
word = "Programming"
for letter in word:
if letter.lower() in "aeiou":
continue # 跳过元音
print(letter, end="")
print() # 末尾换行输出:
Prgrmmng当循环遇到元音字母时,continue 会跳过 print() 语句并移动到下一个字符。只有辅音会执行到打印语句。
下面是一个更实际的示例:在跳过无效数据的同时计算统计值。注意,.split() 方法(来自第 6 章)会返回一个字符串列表,我们会在第 14 章学习。现在你只需要知道 for 循环可以遍历它的结果:
# 计算有效测试分数(0-100)的平均值
# .split() 方法返回一个字符串列表(第 14 章)
scores_input = "85 92 -5 78 105 90 88"
valid_scores = 0
total = 0
for score_str in scores_input.split():
score = int(score_str)
if score < 0 or score > 100:
print(f"Skipping invalid score: {score}")
continue # 跳过该分数
valid_scores += 1
total += score
if valid_scores > 0:
average = total / valid_scores
print(f"Average of {valid_scores} valid scores: {average:.1f}")
else:
print("No valid scores to average")输出:
Skipping invalid score: -5
Skipping invalid score: 105
Average of 5 valid scores: 86.6循环处理每个分数字符串。当遇到无效分数(负数或大于 100)时,它会打印警告并使用 continue 跳过把该分数加入总和的操作。这使循环能够处理所有有效分数,同时忽略无效分数。
12.4.3) 何时使用 break 与 continue
在以下情况下使用 break:
- 你在搜索某个东西,并希望找到后就停止
- 你遇到某种错误条件,使得继续没有意义
- 你已经完成任务,不需要处理剩余项目
在以下情况下使用 continue:
- 你想基于某个条件跳过某些项目
- 你在过滤数据,并希望只处理满足条件的项目
- 你想通过尽早处理特殊情况来避免过深的
if嵌套
break 和 continue 都可以通过明确说明何时以及为何改变迭代的正常流程,使代码更清晰。不过,过度使用它们会让代码更难理解——当它们确实能提升清晰度时再使用。
12.5) 在 for 循环中使用 else
12.5.1) for-else 模式
Python 的 for 循环支持一个可选的 else 子句,它会在循环正常完成后执行——也就是当循环遍历完所有元素、且没有遇到 break 语句时执行。起初这可能显得奇怪(没有 "if" 为什么会有 "else"?),但它对于区分“找到了我要找的东西”和“全部搜索过了但没找到”非常有用。
基本结构如下:
for item in sequence:
# 循环体
if some_condition:
break
else:
# 只有在循环没有 break、正常完成时才会执行
print("Loop completed normally")else 块当且仅当循环通过耗尽序列自然退出时运行。如果循环被 break 提前退出,则会跳过 else 块。
我们来看一个实际示例,用于搜索特定值。.split() 方法(来自第 6 章)会返回一个字符串列表,我们会在第 14 章学习:
# 搜索目标数字
numbers = "2 4 6 8 10"
target = 7
for num_str in numbers.split():
num = int(num_str)
if num == target:
print(f"Found {target}!")
break
else:
print(f"{target} not found in the sequence")输出:
7 not found in the sequence循环搜索所有数字。由于它从未找到 7,因此它会正常完成,并执行 else 块。现在我们搜索一个存在的数字:
# 搜索一个存在的目标数字
numbers = "2 4 6 8 10"
target = 6
for num_str in numbers.split():
num = int(num_str)
if num == target:
print(f"Found {target}!")
break
else:
print(f"{target} not found in the sequence")输出:
Found 6!这一次,当循环找到 6 时,它会执行 break,从而完全跳过 else 块。该模式无需单独的标志变量,就能优雅地同时处理“找到”和“未找到”的情况。
12.5.2) for-else 的实际用途
for-else 模式对搜索操作尤其有用。下面是一个示例,用于验证字符串是否只包含数字:
# 检查字符串是否是有效整数
user_input = "12345"
for char in user_input:
if not char.isdigit():
print(f"Invalid: '{char}' is not a digit")
break
else:
print(f"'{user_input}' is a valid integer")输出:
'12345' is a valid integer如果输入包含任何非数字字符,循环会 break,else 块不会运行。如果所有字符都是数字,循环会正常完成,else 块确认其有效。
我们用无效输入测试一下:
# 检查字符串是否是有效整数(无效情况)
user_input = "123a5"
for char in user_input:
if not char.isdigit():
print(f"Invalid: '{char}' is not a digit")
break
else:
print(f"'{user_input}' is a valid integer")输出:
Invalid: 'a' is not a digit12.5.3) 对比 for-else 与标志变量
在学习 for-else 之前,你可能会用一个标志变量来跟踪是否找到了某个东西:
# 使用标志变量(传统方法)
text = "Python"
found_vowel = False
for char in text:
if char.lower() in "aeiou":
print(f"Found vowel: {char}")
found_vowel = True
break
if not found_vowel:
print("No vowels found")输出:
Found vowel: ofor-else 模式可以省去标志变量:
# 使用 for-else(更 Pythonic)
text = "Python"
for char in text:
if char.lower() in "aeiou":
print(f"Found vowel: {char}")
break
else:
print("No vowels found")输出:
Found vowel: o两种方法都能正确工作,但 for-else 版本更简洁,也更清楚地表达意图:“搜索某个东西,如果没找到就做这件事。”else 子句直接代表“未找到”的情况。
12.6) 在 for 与 while 循环之间做选择
12.6.1) 何时使用 for 循环
在以下情况下使用 for 循环:
1. 你在处理集合中的每个元素:
# 处理字符串中的每个字符
message = "Hello"
for char in message:
print(char.upper())输出:
H
E
L
L
O2. 你需要把某个操作重复执行特定次数:
# 打印一条边框线
for i in range(40):
print("-", end="")
print()输出:
----------------------------------------3. 你在遍历一个数字范围:
# 计算 5 的阶乘
factorial = 1
for n in range(1, 6):
factorial *= n
print(f"5! = {factorial}")输出:
5! = 1204. 你事先知道你在遍历什么:
# 处理一个已知的值序列
grades = "A B C D F"
for grade in grades.split():
print(f"Grade: {grade}")输出:
Grade: A
Grade: B
Grade: C
Grade: D
Grade: Ffor 循环的关键特征是:它适用于 确定性迭代(definite iteration)——你知道你在遍历什么序列,即便你事先不知道该序列包含多少个元素。
12.6.2) 何时使用 while 循环
在以下情况下使用 while 循环:
1. 你要一直重复,直到条件发生变化:
# 持续询问直到收到有效输入
while True:
age_input = input("Enter your age: ")
if age_input.isdigit():
age = int(age_input)
if age > 0:
print(f"Age recorded: {age}")
break
print("Please enter a valid positive number")2. 你不知道需要多少次迭代:
# 统计一个数翻倍多少次后会超过 1000
number = 1
count = 0
while number <= 1000:
number *= 2
count += 1
print(f"Doubled {count} times to reach {number}")输出:
Doubled 10 times to reach 10243. 迭代依赖复杂条件:
# 模拟一个简单游戏:玩家生命值逐步下降
health = 100
turn = 0
while health > 0 and turn < 10:
damage = 15
health -= damage
turn += 1
print(f"Turn {turn}: Health = {health}")
if health <= 0:
print("Game over!")
else:
print("Survived 10 turns!")输出:
Turn 1: Health = 85
Turn 2: Health = 70
Turn 3: Health = 55
Turn 4: Health = 40
Turn 5: Health = 25
Turn 6: Health = 10
Turn 7: Health = -5
Game over!4. 你需要对迭代逻辑有更多控制:
# 处理字符串,但跳过连续重复字符
text = "bookkeeper"
index = 0
while index < len(text):
char = text[index]
print(char, end="")
# 跳过连续相同的字符
while index < len(text) and text[index] == char:
index += 1
print()输出:
bokeperwhile 循环的关键特征是 不确定性迭代(indefinite iteration)——你会一直执行直到条件变为假,但你可能事先不知道这会在什么时候发生。
12.6.3) 在 for 与 while 之间转换
许多问题可以用任意一种循环来解决。下面是同一个任务的两种实现方式:
# 使用 for 循环:求 1 到 10 的和
total = 0
for number in range(1, 11):
total += number
print(f"Sum (for loop): {total}")
# 使用 while 循环:求 1 到 10 的和
total = 0
number = 1
while number <= 10:
total += number
number += 1
print(f"Sum (while loop): {total}")输出:
Sum (for loop): 55
Sum (while loop): 55两者产生相同结果,但 for 循环更简洁且更不容易出错——不会有忘记递增计数器的风险。当两者都能用时,优先选择 for 循环。
不过,有些问题用 while 表达更自然:
# 找到第一个大于 1000 的 2 的幂
power = 1
exponent = 0
while power <= 1000:
exponent += 1
power = 2 ** exponent
print(f"2^{exponent} = {power} (first power of 2 > 1000)")输出:
2^10 = 1024 (first power of 2 > 1000)这个问题并不自然适合用 for 循环,因为我们事先不知道需要迭代多少次——我们是在搜索一个满足条件的值。
在本章中,我们探索了 Python 的 for 循环,它提供了一种干净而强大的方式来遍历序列。我们学习了 range() 如何生成用于计数操作的数值序列,如何用 break 与 continue 控制循环流程,以及 for-else 模式如何优雅地处理搜索操作。我们还基于迭代任务的性质,分析了何时选择 for 循环与 while 循环。
for 循环是 Python 中最常用的特性之一。随着你继续学习 Python,你会不断使用 for 循环——处理来自文件的数据、操作列表和字典,以及转换信息集合。在下一章中,我们将探索 Python 的 match 语句,它提供了另一种基于值进行决策的方法,对于某些类型的问题而言,它是长 if-elif 链的更结构化替代方案。