循环语句
循环语句是编程中的一个基本结构,它允许我们多次执行同一组指令,直到满足某个条件才结束。比如下面的这些应用场合,都非常适合使用循环:
- 处理大量数据: 有一个包含数千或数百万条数据的列表,并且需要对每条数据进行处理,不可能手动为每条数据编写处理代码。使用循环,可以为所有数据编写一次处理逻辑。
- 重复执行: 例如,编写一个游戏,让玩家尝试多次,直到赢得比赛或放弃。可以使用一个循环来允许玩家多次尝试。
- 搜索和查找: 例如,有一个名单,需要查找名单里是否有特定的名字。可以使用循环遍历整个名单直到找到该名字或到达名单的末尾。
- 算法和模拟: 例如,模拟一个实验 1000 次来计算某种结果的概率。可以使用循环来执行每一次的模拟。
Python 提供了几种循环机制,主要包括 for 循环和 while 循环。
for 循环
可迭代对象和迭代器
Python 中的迭代器(iterator)是一种数据对象,它们都有一个共同的方法,称为“获取下一个”,调用此方法即可获取下一个数据。可以想象,如果有一组数据,如果使用迭代器,每次找到当前数据的下一个数据,这样持续下去,就可以遍历这组数据中的每一个数据。目前,我们只需要大致了解其概念,后续将专门开设一节,专门讲解迭代器的工作原理。
可迭代对象(iterable)指的是任何可以被迭代(即遍历其成员元素)的对象。可迭代对象可以返回一个迭代器,使用迭代器访问可迭代对象中的每个数据。我们之前介绍过的列表、元组、字符串都是可迭代对象;将来会介绍的一些数据,比如字典、集合、文件对象等,也都是可迭代对象。
遍历可迭代对象的数据
for 循环是 Python 中最常用的循环结构之一。它用于迭代一个序列(列表、元组、字典、集合、字符串)或其他可迭代对象,并在每次迭代中执行代码块。以下是 for 循环的基本结构:
for variable in iterable:
# 循环体代码
其中 variable 是每次迭代中从 iterable 获取的当前元素。
比如,使用 for 循环遍历列表:
fruits = ['苹果', '香蕉', '桔子']
for fruit in fruits:
print(fruit)
# 输出:
# 苹果
# 香蕉
# 桔子
使用 for 循环遍历字符串:
word = "python"
for letter in word:
print(letter)
# 输出:
# p
# y
# t
# h
# o
# n
我们通常可以在循环中显示迭代对象的数据、将数据处理后保存、发送到其他设备,或生成一个新的列表等。但通常不建议在循环中修改、删除或增加被迭代对象,因为此类操作可能导致意外结果。循环会迭代修改前的数据还是修改后的数据呢?如果被迭代对象长度发生变化,那么下一个数据应该是谁呢?比如,读者可以猜测下面的程序运行结果是什么:
fruits = ['苹果', '香蕉', '桔子']
for fruit in fruits:
fruits[2:2] = '西瓜'
print(fruit)
上面这个程序会导致死循环。所以,最好不要在循环内增删被迭代对象,如果需要修改,可以使用列表的切片等相应方法,或者推导式、高阶函数等方法进行修改。
range() 函数
range() 是 Python 的内置函数,返回一个迭代器,用于生成一系列按指定步进排列的整数。这个函数在循环中特别有用。
range() 函数的基本语法如下: range([start,] stop [, step])
。其中
- start (可选):起始值,默认为0。
- stop:结束值(不包括)。
- step (可选):步进值,默认为1。
使用示例:
# 生成从0开始的连续整数
for i in range(5):
print(i)
# 输出: 0 1 2 3 4
# 指定开始和结束值
for i in range(2, 5):
print(i)
# 输出: 2 3 4
# 指定开始、结束和步进值
for i in range(0, 10, 2):
print(i)
# 输出: 0 2 4 6 8
# 使用负数作为步进值
for i in range(5, 1, -1):
print(i)
# 输出: 5 4 3 2
需要注意的是: range() 生成的序列不包括 stop 指定的值。在 Python 2 中,range() 返回一个列表;而在 Python 3 中,它返回一个迭代器,不实际存储整个数字序列,从而节省内存。因此,如果直接打印 range() 函数,是不会看到期望的数列的。需要使用 for 循环来迭代 range() 函数生成的所有数据。或者可以使用 list() 函数把它转换成列表:
print(range(3)) # 输出: range(0, 3)
print(list(range(3))) # 输出: [0, 1, 2]
enumerate() 函数
很多编程语言,比如 C 语言,在循环中,主要使用一个由变量表示的索引值去获取所需数据。比如:
for (int i=0; i<10; i++) {
printf(array[i]);
}
上面的 C 代码中,整数变量 i 的值会在每次迭代时,从 0 依次增加到 9,然后循环中使用 i 去索引输入数据 array,从而得到每次迭代所需的数据。
有的用户可能会在 Python 中沿用这样的习惯,写出如下代码:
fruits = ['苹果', '香蕉', '草莓', '桔子']
for i in range(len(fruits)):
print(f"第 {i} 个水果是: {fruits[i]}")
# 输出:
# 第 0 个水果是: 苹果
# 第 1 个水果是: 香蕉
# 第 2 个水果是: 草莓
# 第 3 个水果是: 桔子
但实际上,Python 是利用可迭代数据来控制循环的,它并不需要一个索引变量。硬要使用索引变量,代码效率不高,也不符合 Python 的编码习惯,会降低程序可读性。如果在循环体内,只需要用到列表里的数据,可以使用 for fruit in fruits:
直接得到每个数据;如果在循环体内需要知道每个数据的索引值,可以使用 enumerate 函数得到一个带索引的可迭代对象:
fruits = ['苹果', '香蕉', '草莓', '桔子']
for i, fruit in enumerate(fruits):
print(f"Element {i} is {fruit}")
# 输出:
# 第 0 个水果是: 苹果
# ......
虽然计算机总是从 0 开始索引,但我们的自然习惯还是从 1 开始计数。如果需要打印结果更符合人类阅读习惯,可以为 enumerate 添加一个输入,指定索引从几开始,比如:
fruits = ['苹果', '香蕉', '草莓', '桔子']
for i, fruit in enumerate(fruits, 1):
print(f"Element {i} is {fruit}")
# 输出:
# 第 1 个水果是: 苹果
# 第 2 个水果是: 香蕉
# 第 3 个水果是: 草莓
# 第 4 个水果是: 桔子
zip() 函数
在一个循环内处理多个可迭代对象,也同样不需要使用索引变量。符合 Python 习惯的代码是使用 zip() 函数。zip() 函数用于将多个可迭代对象的元素配对,返回一个新的迭代器,产生由各个可迭代对象中对应位置的元素组成的元组。比如:
a = [1, 2, 3]
b = ['a', 'b', 'c']
result = list(zip(a, b))
print(result) # 输出:[(1, 'a'), (2, 'b'), (3, 'c')]
如此,我们就只要在循环中迭代这个 zip() 函数返回的迭代器,就可以依次拿到所有的数据了。下面是一个使用 zip 函数的示例,它遍历两个列表:一个包含学生姓名,另一个包含他们的成绩。zip 将这两个列表的元素一一对应起来,使得我们可以在一个 for 循环中同时访问每个学生的姓名和成绩。
students = ['张三', '李四', '王五', '赵六', '小明']
scores = [90, 85, 88, 92, 95]
for student, score in zip(students, scores):
print(f'{student} 的成绩是: {score}')
# 这段代码的输出会是:
# 张三 的成绩是: 90
# 李四 的成绩是: 85
# 王五 的成绩是: 88
# 赵六 的成绩是: 92
# 小明 的成绩是: 95
如果两个列表的长度不同,zip 会在到达任何一个列表的末尾时停止配对。 若希望按最长的列表配对,可以使用 itertools 模块中的 zip_longest() 函数,它会以默认值补全较短列表的缺失数据。比如:
from itertools import zip_longest
list1 = [1, 2, 3, 4]
list2 = ['a', 'b', 'c']
# 使用 zip_longest 进行组合,并用 None 填充较短列表的缺失部分
combined = list(zip_longest(list1, list2))
print(combined) # 输出: [(1, 'a'), (2, 'b'), (3, 'c'), (4, None)]
# 使用一个默认值来填充缺失的元素:
combined = list(zip_longest(list1, list2, fillvalue="缺失"))
print(combined) # 输出: [(1, 'a'), (2, 'b'), (3, 'c'), (4, '缺失')]
break 语句
当 break 语句在循环中被执行,它会立刻终止所在的循环,跳出循环。程序会继续执行循环之后的代码。break 最常见的应用场景是:若循环过程中已满足预设条件,比如已经找到了要找的数据,那就不需要再执行后面的循环迭代了,直接跳出循环可以节省程序运行时间。
下面的程序在找到第一个偶数后中断循环:
numbers = [1, 3, 7, 9, 2, 5, 6]
for num in numbers:
if num % 2 == 0:
print(f"找到一个偶数:{num}")
break
# 输出:
# 找到一个偶数:2
continue 语句
当 continue 语句在循环中被执行,当前迭代会被中断,然后进入下一次迭代。
下面的程序打印出所有的奇数,并跳过偶数。
numbers = [1, 2, 3, 4, 5]
for num in numbers:
if num % 2 == 0:
continue
print(num)
# 输出:
# 1
# 3
# 5
带有 else 子句的 for 循环
for 循环可以带一个 else 块,当循环正常完成(没有被 break 语句中断)时执行。
for i in range(3):
print(i)
else:
print("循环正常结束")
# 输出:
# 0
# 1
# 2
# 循环正常结束
循环带着一个 else 子句是比较独特的,在其它编程语言中比较少见。而且,这个 else 比较容易引起歧义,直觉的理解可能是:如果条件不满足就进入 else 块,那就应该是循环出了意外则进入 else;但实际上恰恰相反,它真正的行为是当循环正常完成时,进入 else 子句。此外,当迭代次数为 0(即输入的可迭代对象为空)时,程序也会进入 else 子句。这的确有点令人迷惑,所以有人建议始终避免在循环语句后面添加 else 块,毕竟它不是必须的,其它语言都没有这个功能。
不过,在熟悉了它的用法之后,它还是有一些好处的,可以让某些程序逻辑更清晰简洁。最常见的用法是在列表里搜索某个目标数据,如果找到做一些事,然后跳出循环;如果没找到,程序会进入 else 块,我们就可以在这里加入处理没找到情况的代码,比如打印一些信息等:
def search_in_list(lst, target):
for index, value in enumerate(lst):
if value == target:
print(f'找到目标值 {target},索引为 {index}')
break
else: # 如果循环没有被break终止,则执行这个分支
print('没找到')
# 测试:
my_list = [1, 2, 3, 4, 5]
search_in_list(my_list, 3) # 应该找到目标值 3
search_in_list(my_list, 6) # 输出: “没找到”
while 循环
基本用法
while 循环会持续执行,直到其后面的条件为 False。
基本语法:
while 条件表达式:
# 循环体代码块
当条件表达式为 True 时,循环体的代码块会被执行。每次执行完循环体后,条件表达式会再次被评估。只有当条件变为 False 时,循环才会结束。
示例:
count = 0
while count < 5:
print(count)
count += 1
# 输出:
# 0
# 1
# 2
# 3
# 4
break 和 continue
与 for 循环一样,while 循环中也可以使用 break 和 continue。
比如使用 break 提前退出循环:
count = 0
while count < 5:
if count == 3:
break
print(count)
count += 1
# 输出:
# 0
# 1
# 2
使用 continue 跳过当前迭代:
count = 0
while count < 5:
count += 1
if count == 3:
continue
print(count)
# 输出:
# 1
# 2
# 4
# 5
死循环
在编程中,死循环(也称为无限循环)是指一个永远不会自动终止的循环。在 Python 中,死循环通常是因为循环的终止条件从未被满足或者循环体中缺少了改变循环条件的代码。
下面是一个简单的死循环程序:
x = 10
while x > 5:
print(x)
# x 的值没有变化,导致这个循环永远不会结束
编写 while 循环时,需特别注意避免死循环。要始终检查循环条件和循环体,确保循环有明确的退出条件。 可以使用 break 语句在满足某些特定条件时跳出循环,避免死循环。
for 语句同样也可能陷入死循环,Python 中存在一些无限迭代器,它们能产生无限长度的可迭代对象。如果让 for 循环去迭代一个无限长的可迭代对象,它就会陷入死循环。
如果程序陷入了死循环,可以手动停止它。在大多数开发环境和命令行中,可以使用 Ctrl + C 来中断正在运行的程序。
练习
- 乘法表
使用循环语句打印 9*9 乘法表
- 水仙花数
水仙花数,也叫“超完全数字不变数”,是指一个 n 位正整数,其各个位上数字的 n 次方之和等于它本身。例如,三位数的水仙花数就是一个三位数,其各个位上的数字的立方和等于这个数本身。
举例说明:
- 153 是一个三位数,它的各个位数字分别是 1、5、3,满足 ,所以 153 是水仙花数。
- 370 也是一个三位水仙花数,因为 。
编写一个程序找到 100 到 999 之间的所有水仙花数。
- 猴子偷桃
猴子发现一棵桃树,第一天吃了树上一半的桃子后仍不满足,又多吃了一个。第二天早上又将剩下的桃子吃掉一半,再加一个。以后每天早上都吃掉剩下的一半再加一个。到了第 n 天早上,发现树上只剩下 1 个桃子。问树上原本有多少个桃子?
- 打印因数
两个正整数相乘,那么这两个数都叫做积的因数,或称为约数。编写程序,找出数字 1200 的所有因数。
- 质因数分解
编写程序,将一个整数分解为若干个质数的乘积。比如,数字 12 的质因数包括:2, 2, 3。
- 加权平均: 给定一个数值列表
values = [4, 5, 6]
和权重列表weights = [0.2, 0.3, 0.5]
,计算加权平均值。