控制流
你写到现在的每个程序都是这样执行的:从上到下,一行一行地执行。这对简单脚本没问题,但真实的程序需要做决策和重复工作。一个测验需要判断答案对不对。一个游戏需要一直运行到玩家赢或输。这章讲的就是怎么让程序分支和循环。
比较
在做决策之前,你需要比较东西。比较操作符返回 True 或 False。最重要的一个要早点搞对:= 赋值,== 检查两个值是否相等。搞混这两个是初学者最常见的错误之一。
5 > 3 # True
5 < 3 # False
5 == 5 # True (注意:双等号;= 是赋值,== 是比较)
5 != 3 # True ("不等于")
5 >= 5 # True ("大于等于")
5 <= 4 # False ("小于等于")= 和 == 这个区别会把几乎所有人都绊倒。赋值(=)存储一个值;比较(==)检查两个值是否相同。
你也可以比较字符串。Python 按字母顺序逐字符比较它们,字符串章会更详细地讲:
"apple" == "apple" # True
"apple" < "banana" # True (a 在 b 前面)
"apple" == "Apple" # False (区分大小写)= 存储一个值,== 检查两个值是否相等。我在累的时候还是会停下来反复检查这个,所以如果你被绊倒了你并不孤独。比较返回 True 或 False,对字符串也一样有效。 组合条件
and、or 和 not 组合比较。and 要求两边都是真。or 要求至少一边是真。not 翻转结果。这些让你能表达真实的条件,比如"分数及格 AND 用户活跃"。
age = 25
score = 88
age >= 18 and score >= 80 # True (两边都要是真)
age < 18 or score >= 80 # True (至少一边要是真)
not age >= 18 # False (翻转结果)and 要求两边。or 要求至少一边。not 反转。
and 要两边都真,or 要至少一边真,not 翻转答案。这覆盖了你会写的大多数真实条件,比如"分数及格 and 用户活跃"。Python 一旦知道结果就停止检查。 真值和假值
Python 里每个值都有一个布尔解读,即使它不是 True 或 False。空字符串、零、空列表和 None 在条件里的表现都像 False。其他一切都表现像 True。这意味着 if results: 检查列表是否非空,不用写 if len(results) > 0:。
# 这些在条件里都表现得像 False:
False, 0, 0.0, "", [], {}, (), None
# 其他一切都表现得像 True这意味着 if results: 是个自然的说法表示"如果列表里有东西",而 if name: 检查字符串是否有内容。
False:0、""、[] 和 None。其他一切都算 True。这就是为什么 if results: 读起来那么干净,好像"如果列表里有东西",不需要 len()。 if / elif / else
if 语句只在它的条件是 True 时才运行一个代码块。elif 在第一个条件是假时添加更多条件来检查。else 捕获所有没有匹配任何条件的情况。Python 用缩进而不是花括号来定义什么属于每个块。
score = 87
if score >= 90:
print("A 级")
elif score >= 80:
print("B 级")
elif score >= 70:
print("C 级")
else:
print("C 以下")规则:
if是必需的并且总是在前面elif("else if" 的简写)是可选的,你可以想要多少就多少else是可选的,处理所有没有匹配的东西,放在最后- Python 用缩进(4 个空格)来标记属于每个块的东西;没有花括号
缩进不是可选的或只是装饰。Python 用它来定义结构。缩进不一致是个语法错误。
if 在条件是真时运行它的块,elif 检查更多情况,else 捕获剩下的。只有第一个匹配运行。我早期被绊倒的东西:缩进是告诉 Python 每个块从哪儿开始和结束的,所以保持一致。 单行条件
对于简单的是/否赋值,Python 有个叫三元表达式的紧凑单行形式:值_如果真 if 条件 else 值_如果假。只在逻辑很小读起来像句子时才用它。
label = "pass" if score >= 50 else "fail"这是个三元表达式;它读起来像句子。在逻辑很小时用它。对任何涉及 elif 的东西,写完整的版本。
值_如果真 if 条件 else 值_如果假 在单行里挑两个值之一。对整洁的小选择很美,比如 "pass" if score >= 50 else "fail"。逻辑一长就回到常规 if 块,它读起来会更好。 while 循环
while 循环只要它的条件是 True 就重复它的块。用它当你不知道循环应该运行多少次时,比如等待有效输入或重试直到一个任务成功。
lives = 3
while lives > 0:
print(f"剩余生命数:{lives}")
lives -= 1
print("游戏结束")while 在你不知道循环会运行多少次时最好。当你知道时,或当你在遍历一个集合时,for 更干净。
while 只要条件保持真就一直运行它的块。在你不知道需要多少轮时用它,比如等待有效输入。确保循环内部有东西向退出前进,不然它会永远运行,这是我做过不止一次的错误。 break 和 continue
break 立即退出循环,不管还有多少次迭代剩余。continue 跳过当前迭代的剩余部分并跳回条件检查。两者都只影响它们所在的最内层循环。
break 立即退出循环:
target = 5
num = 0
while True:
num += 1
if num == target:
print(f"找到了 {target}")
break # 停止循环while True: 加上 break 在退出条件很复杂或需要在循环体末尾发生时是个有效的常见模式。
continue 跳过当前迭代的剩余部分并回到条件检查:
num = 0
while num < 10:
num += 1
if num % 2 == 0:
continue # 跳过偶数
print(num) # 只有奇数打印:1, 3, 5, 7, 9break 立刻跳出循环,continue 跳过这一轮的剩余并回到条件检查。两者都只影响它们直接在里面的循环。while True: 加 break 初看时读起来很奇怪,但它是一个循环直到什么发生的常规方式。 for 循环
for 循环一次遍历一个序列中的一项:一个列表、一个字符串、一个数字范围。你在 for 之后命名的变量一个接一个地接收每一项。你不用管理计数器或自己检查长度。
players = ["张三", "李四", "王五"]
for player in players:
print(f"你好,{player}!")for 循环也在字符串上工作(一次迭代一个字符)和其他任何序列类型上。
for item in sequence 一次遍历每一项,你永远不碰一个计数器或长度自己。它在列表、字符串、范围上工作,很多东西。从其他语言来的,不管理索引一开始感觉很奇怪,然后感觉像礼物。 range()
range() 为你生成一个数字序列来循环遍历。range(5) 给你 0, 1, 2, 3, 4。你可以控制起点、终点和步长。用它当你需要循环运行特定次数时。
for i in range(5):
print(i) # 0, 1, 2, 3, 4range() 有三种形式:
| 调用 | 它生成什么 |
|---|---|
range(5) | 0, 1, 2, 3, 4 |
range(2, 6) | 2, 3, 4, 5 |
range(0, 10, 2) | 0, 2, 4, 6, 8(步长为 2) |
range(5, 0, -1) | 5, 4, 3, 2, 1(倒数) |
range() 不创建列表。它一次生成一个数字,对非常大的范围也有效。
range(5) 给你 0, 1, 2, 3, 4,你可以设置起点、终点和步长。在你想循环运行设定次数时用它。它不建立列表,每轮给你一个数字。 enumerate()
enumerate() 在你循环时同时给你索引和值,所以你不需要分开追踪计数器。i, player 部分自动在每次迭代接收一对值。
players = ["张三", "李四", "王五"]
for i, player in enumerate(players):
print(f"{i + 1}. {player}")
# 1. 张三
# 2. 李四
# 3. 王五i, player 语法叫解包。Python 自动把 (index, value) 对分成两个名字。
默认 enumerate() 从 0 开始。传一个起值改变那个:
for i, player in enumerate(players, start=1):
print(f"{i}. {player}") # 从 1 开始enumerate() 同时给你位置和值,所以无单独计数器要保持同步。i, player 部分一次抓两个。当你给某人编号列表时传 start=1。 嵌套循环
你能把一个循环放在另一个循环里。内循环对外循环的每一次迭代都完整运行。这是怎么处理网格、组合或任何两层结构的数据的。
rows = [1, 2, 3]
cols = ["A", "B"]
for row in rows:
for col in cols:
print(f"{col}{row}", end=" ")
print() # 每行后换行
# A1 B1
# A2 B2
# A3 B3嵌套循环内的 break 和 continue 只影响最内层循环。
break 和 continue 只作用在它们坐着的循环,这里是内循环。 循环-else
Python 循环能有一个 else 子句只在循环没碰到 break 时运行。它不常用,但是写"搜索列表,如果什么都没找到,做这个"的最干净方式。
target = "王五"
names = ["张三", "李四", "赵六"]
for name in names:
if name == target:
print(f"找到了 {target}")
break
else:
print(f"{target} 不在列表里") # 运行因为 break 永不触发如果 break 运行,else 被跳过。如果循环排干序列,else 运行。它是个利基模式但比标志变量更干净。
else 只在循环无 break 时结束运行。不常用,但最整洁的说法是"搜索这个列表,如果什么都没匹配,做这个"。名字有点误导,所以短评论有帮助。 排序
sorted() 返回一个新排序列表并保留原列表不变。.sort() 就地排序列表并返回 None。key= 参数让你按其他于原始值的东西来排序。比如排序名字不区分大小写或按分数排序玩家元组。
scores = [87, 42, 96, 55, 71]
ranked = sorted(scores) # [42, 55, 71, 87, 96] (新列表)
scores.sort() # 排序原始列表,返回 None
scores.sort(reverse=True) # [96, 87, 71, 55, 42]两者都接受 key= 参数:一个在比较前应用到每项的函数:
names = ["Charlie", "Alice", "Bob"]
sorted(names, key=str.lower) # 不区分大小写排序
players = [("Alice", 87), ("Bob", 96), ("Charlie", 55)]
sorted(players, key=lambda p: p[1]) # 按分数排序lambda p: p[1] 是一个单行函数:它接受一个玩家元组并返回分数。Lambdas 在Lambdas 和列表推导章得到充分对待。
对简单情况,用 sorted()。对想就地修改的列表,用 .sort()。
sorted() 交付一个新排序列表并保留原列表,.sort() 就地重排列表并返回 None。传 key= 来按其他于原值的东西排序,比如名字不区分大小写。不确定时,去找 sorted(),它是安全的。 实践中
循环遍历分数,累计一个总数,计数及格级别,打印一个总结:
raw_scores = [87, 42, 96, 55, 71, 63]
total = 0
passing = 0
for score in raw_scores:
total += score
if score >= 60:
passing += 1
average = total / len(raw_scores)
print(f"平均:{average:.1f}")
print(f"及格:{passing}/{len(raw_scores)}")
print(f"最高分:{sorted(raw_scores, reverse=True)[0]}")
