while 循环
while 循环在条件为真时反复执行一段代码。与 for 适合"遍历已知集合"不同,while 更适合 循环次数未知、依赖条件变化 的场景,比如读取输入、轮询状态、迭代收敛等。
while 基本语法
while 后跟布尔表达式,只要为 True 就反复执行缩进代码块:
count = 0
while count < 5:
print(f"第 {count + 1} 次循环")
count += 1
输出:
第 1 次循环
第 2 次循环
第 3 次循环
第 4 次循环
第 5 次循环
:::warning 不要忘记更新条件变量
while 循环必须确保条件最终会变为 False,否则会无限循环。如果忘记写 count += 1,上面的代码会一直打印"第 1 次循环"。
:::
无限循环
用 while True: 可以构造无限循环,配合 break 在合适时机跳出:
while True:
user_input = input("请输入命令(输入 quit 退出):")
if user_input == "quit":
print("再见!")
break
print(f"你输入了:{user_input}")
这种写法常用于交互式程序、游戏主循环、服务器请求处理等场景。
:::tip 何时用 while True
当循环条件复杂、需要在循环体中部判断退出时,while True: + break 比把所有条件塞进 while 后面更清晰。例如读取输入直到满足某条件:
while True:
value = int(input("请输入正数:"))
if value > 0:
break
print("输入无效,必须为正数。")
:::
while...else 语句
与 for 一样,while 也可以带 else 子句,仅在循环 因条件为 False 而正常退出(没有被 break 打断)时执行:
n = 0
while n < 5:
print(n, end=" ")
n += 1
else:
print("\n循环正常结束。")
# 输出:
# 0 1 2 3 4
# 循环正常结束。
一个实际应用是查找元素:
def find_first_even(numbers: list[int]) -> int | None:
"""返回第一个偶数,找不到返回 None。"""
i = 0
while i < len(numbers):
if numbers[i] % 2 == 0:
return numbers[i]
i += 1
else:
print("未找到偶数。")
return None
find_first_even([1, 3, 5, 6]) # 返回 6
find_first_even([1, 3, 5]) # 打印"未找到偶数。"并返回 None
:::note while...else 的争议
while...else 的语义"非 break 退出时执行 else"对初学者不太直观。Python 之禅也强调"可读性很重要"。在大型项目中,更推荐用标志变量或函数提前返回来替代,避免使用 while...else。
:::
避免死循环
死循环是 while 最容易出现的 bug。常见原因:
- 忘记更新条件变量:循环体没改变条件中使用的变量。
- 更新方向错误:本应递增却写成了递减。
- 浮点数比较误差:条件可能永远满足。
# 反例 1:忘记更新 count
# count = 0
# while count < 5:
# print(count) # 无限循环!永远打印 0
# 反例 2:浮点数累加误差
total = 0.0
step = 0.1
target = 1.0
# 这种写法可能因浮点误差永远达不到 target
while total != target:
total += step
if total > 10: # 安全保护
print("异常终止")
break
:::warning 浮点数比较陷阱
不要用 == 比较浮点数。0.1 + 0.1 + 0.1 在 Python 中等于 0.30000000000000004,不严格等于 0.3。应使用容差比较或整数计数:
# 推荐做法:用整数计数
n = 0
while n < 10:
total = n * 0.1
print(total)
n += 1
:::
为防止死循环,可以为循环加上"最大迭代次数"的安全阀:
max_iterations = 1000
iteration = 0
while some_condition() and iteration < max_iterations:
do_something()
iteration += 1
if iteration >= max_iterations:
print("警告:达到最大迭代次数,可能未收敛。")
海象运算符在 while 中的应用
Python 3.8 引入的海象运算符 :=(赋值表达式)能在表达式内部完成赋值,特别适合 while 循环读取输入或处理可迭代值。
传统写法
text = input("输入内容:")
while text != "quit":
print(f"你输入了:{text}")
text = input("输入内容:")
需要写两次 input(),重复且容易出错。
使用海象运算符
while (text := input("输入内容:")) != "quit":
print(f"你输入了:{text}")
一行完成"读取并判断",代码更紧凑。
处理分块数据
海象运算符在分块读取数据时也很有用:
def read_lines(data: list[str], chunk_size: int) -> None:
"""每次读取 chunk_size 行并处理。"""
index = 0
while (chunk := data[index:index + chunk_size]):
print(f"处理块:{chunk}")
index += chunk_size
read_lines(["a", "b", "c", "d", "e"], chunk_size=2)
输出:
处理块:['a', 'b']
处理块:['c', 'd']
处理块:['e']
当切片为空时,chunk 为空列表(falsy),循环自动终止。
:::tip 海象运算符的适用场景 海象运算符特别适合以下场景:
while中读取输入并判断- 列表推导式中需复用计算结果
- 条件表达式中需要保存中间值
但它会让代码更紧凑,也要注意不要为了"炫技"而牺牲可读性。 :::
实战:猜数字游戏
综合运用 while 循环、海象运算符、条件判断,实现一个经典的猜数字游戏:
import random
def guess_number_game() -> None:
"""猜数字游戏:随机生成 1~100 的数字,玩家猜测直到猜中。"""
target = random.randint(1, 100)
attempts = 0
max_attempts = 7
print("欢迎来到猜数字游戏!")
print(f"我已经想好了一个 1~100 之间的数字,你有 {max_attempts} 次机会。")
while attempts < max_attempts:
attempts += 1
remaining = max_attempts - attempts
try:
guess = int(input(f"第 {attempts} 次猜测(剩余 {remaining} 次):"))
except ValueError:
print("请输入有效的整数。")
attempts -= 1 # 不消耗机会
continue
if guess == target:
print(f"恭喜!你用 {attempts} 次猜中了数字 {target}。")
return
elif guess < target:
print(f"太小了!剩余 {remaining} 次。")
else:
print(f"太大了!剩余 {remaining} 次。")
print(f"很遗憾,机会用尽。正确答案是 {target}。")
if __name__ == "__main__":
guess_number_game()
示例运行:
欢迎来到猜数字游戏!
我已经想好了一个 1~100 之间的数字,你有 7 次机会。
第 1 次猜测(剩余 6 次):50
太小了!剩余 6 次。
第 2 次猜测(剩余 5 次):75
太大了!剩余 5 次。
第 3 次猜测(剩余 4 次):63
恭喜!你用 3 次猜中了数字 63。
:::info 二分查找策略 在 1~100 之间,用二分查找(每次猜中间值)最多需要 ⌈log₂100⌉ = 7 次。所以本游戏给 7 次机会,掌握二分策略的玩家总能赢。 :::
:::tip 使用 try-except 处理输入
用户输入是不可控的,应该用 try-except 捕获 ValueError,避免程序因非法输入崩溃。这里用了 continue 跳过本次循环且不消耗机会。
:::
小结
while适合循环次数未知、依赖条件变化的场景。while True:+break适合在循环体中部判断退出的场景。while...else在循环正常结束时执行,但语义不直观,慎用。- 必须确保循环条件最终为
False,避免死循环;可加最大迭代次数作为安全阀。 - 浮点数比较要用容差,不要直接用
==。 - 海象运算符
:=能简化"读取-判断"循环,是 Python 3.8+ 的实用特性。