本节摘要:流程控制决定程序"怎么走",字符串处理决定程序"怎么处理文本"。本节讲清楚
if/elif/else分支、for与while循环的选用原则,以及字符串的切片、查找、替换、格式化。通过简易计算器和文本清洗两道练习,把"用循环组织重复、用字符串操作文本"的能力练出来。
阅读完本节,你应当能够:
for 和 while 的适用场景,做出合理选择break 和 continue 精确控制循环的跳出与跳过回想一下你写过或看过的任何程序——它几乎不会是"从上到下一条直线走完"的。真实程序充满了选择和重复:用户输入对了走这条分支,错了走那条;一组数据要逐个处理;某个操作在条件满足前要一直做。如果没有流程控制,程序就只能做一次性、固定路径的计算,那它几乎什么都干不了。
字符串处理也是同样的地位。Python 程序打交道最多的数据就是文本:用户输入是文本,读到的文件内容是文本,要展示给用户的结果也常常要拼成文本。能不能熟练地切、找、替、拼字符串,直接决定你写小程序的效率。
这两个主题放一起讲,是因为它们常常配合出现:循环遍历一个字符串的每个字符、在循环里根据条件拼接结果文本——这是文本处理类任务的典型形态。
分支结构本身不复杂,新手真正要掌握的是判断条件的顺序和边界。看一个反面例子:
score = 85 if score >= 60: print("及格") elif score >= 90: print("优秀") # 永远走不到这里
85 >= 60 先成立,直接进了"及格"分支,后面的 elif 根本不会被检查。分支条件要从严到宽排列,否则宽条件会"吃掉"后面所有更细的条件。
Python 有两种循环,它们的分工原则很清晰:
| 循环类型 | 适用场景 | 典型例子 |
|---|---|---|
for |
已知要遍历的范围或序列 | 遍历列表、计数循环、逐字符处理字符串 |
while |
只知道继续条件,不知道次数 | 不断读取输入直到用户退出、等待某条件成立 |
# for:明确知道要循环 10 次 for i in range(10): print(i) # while:不知道用户什么时候结束输入 while True: cmd = input("请输入命令(q 退出):") if cmd == "q": break print(f"执行:{cmd}")
💡 关键直觉:能用
for就别用while。for循环天然有明确的终止条件(遍历完序列就结束),不容易写出死循环;while一旦忘记更新条件变量,很容易把程序卡死。
这两个控制语句改变循环的默认行为,但用法要克制:
# break:彻底跳出循环 for n in range(2, 100): if n * n > 100: break # 找到第一个平方超过 100 的数就停 # continue:跳过本次,进入下一次 for n in range(10): if n % 2 == 0: continue # 跳过偶数 print(n) # 只打印奇数
滥用 continue 会让循环逻辑支离破碎。能用 if ... else 表达清楚的分支,就别用 continue 跳来跳去。
字符串是不可变序列,它的操作分三类:
切片是字符串里最高频的操作,记住一个公式:s[start:stop:step],左闭右开,三个参数都可省略。
s = "Hello, Python" print(s[0:5]) # "Hello" 从 0 取到 4 print(s[7:]) # "Python" 从 7 取到末尾 print(s[::-1]) # 反转字符串
split 和 join 是一对反操作,常用于文本与列表的相互转换:
words = "apple,banana,cherry".split(",") # ['apple', 'banana', 'cherry'] result = "-".join(words) # "apple-banana-cherry"
格式化输出用 f-string,它能在字符串里直接嵌入表达式:
name, age = "小明", 18 print(f"姓名:{name},年龄:{age},明年 {age + 1} 岁")
这是经典陷阱。边遍历边删元素,会导致跳过某些元素:
nums = [1, 2, 3, 4, 5] for n in nums: if n % 2 == 0: nums.remove(n) # 想删偶数,但会漏掉相邻的偶数
正确做法是遍历副本或用推导式生成新列表(推导式第 4 章细讲,这里先用 for 收集):
result = [] for n in nums: if n % 2 != 0: result.append(n)
⚠️ 常见坑:
while循环忘更新条件变量,导致死循环。写while时第一件事先确认"这个循环在每一步都会让条件向 False 靠近"。
少量拼接用 + 没问题,但在循环里反复 + 拼接长字符串会很慢(每次 + 都创建新对象)。大量拼接用 join:
# 慢:循环里反复 + result = "" for word in words: result += word # 快:一次性 join result = "".join(words)
字符串方法返回新字符串,可以链式调用。但要保证每一步都不报错——比如 find 找不到返回 -1,直接对 -1 切片会得到意想不到的结果:
s = "hello world" pos = s.find("Python") # -1,没找到 print(s[pos:]) # "hello world",因为 s[-1:] 取最后,不是预期
链式调用时,先用 find 判断是否找到,再切片才安全;或者直接用 in 判断存在性,避免 -1 带来的歧义。
range 与 enumerate 的选用循环处理序列时,"要不要索引"决定了用哪种写法:
fruits = ["苹果", "香蕉", "橘子"] # 不需要索引:直接遍历元素 for f in fruits: print(f) # 需要索引:用 range 配合 len(啰嗦,不推荐) for i in range(len(fruits)): print(i, fruits[i]) # 需要索引:用 enumerate(Pythonic,推荐) for i, f in enumerate(fruits): print(i, f)
enumerate 同时给你"序号和元素",比 range(len(...)) 更清楚也更难出错。序号还能指定起点:enumerate(fruits, 1) 从 1 开始计数。
💡 关键直觉:凡是"既要索引又要元素"的循环,默认用
enumerate。range(len(...))是从 C/Java 带过来的习惯,在 Python 里是代码味道(code smell)。
题面:写一个程序,反复读取用户输入的"运算表达式",格式为 数字 运算符 数字(如 3 + 5),计算并输出结果。直到用户输入 q 退出。运算符支持 + - * /。
思路点拨:典型的 while 循环题。每轮先读输入、判断是否退出;不退出就 split() 拆成三部分,做类型转换和运算。除法要小心除零。
参考骨架:
while True: expr = input("输入(如 3 + 5,q 退出):") if expr == "q": break parts = expr.split() if len(parts) != 3: print("格式不对") continue a, op, b = float(parts[0]), parts[1], float(parts[2]) # TODO: 根据 op 做对应运算,注意除零 if op == "+": print(a + b) # 继续补全 - * /
题面:给定一段混乱的文本(有多余空格、大小写不一、含标点),写程序:去除首尾空格、把连续多个空格压成一个、全部转小写、统计清洗后有多少个单词。
示例输入:" Hello World, Python is GREAT! "
思路点拨:链式调用字符串方法最简洁。strip 去首尾、split 默认按任意空白分割(自动压缩连续空格)、lower 转小写。单词数就是 split 后列表的长度。
参考骨架:
text = " Hello World, Python is GREAT! " cleaned = text.strip().lower() words = cleaned.split() # 默认按空白分,自动压缩 print(f"清洗后:{cleaned}") print(f"单词数:{len(words)}")
💡 思考延伸:如果要进一步去掉标点呢?这就要遍历每个字符判断是否字母或空格——循环加字符串操作的经典组合,自己试试。
题面:给定一个水果列表,用 enumerate 打印"第 1 个:苹果"这样的带序号输出;再写一段代码,找出某个指定水果第一次出现的序号,找不到提示"没有"。
思路点拨:enumerate(..., 1) 从 1 计数。查找时遍历比较,找到就记录序号并跳出。
参考骨架:
fruits = ["苹果", "香蕉", "橘子", "香蕉"] for i, f in enumerate(fruits, 1): print(f"第 {i} 个:{f}") target = "香蕉" found = None for i, f in enumerate(fruits, 1): if f == target: found = i break print(f"{target} 第一次出现在第 {found} 个" if found else "没有")
⚠️ 常见坑:列表自带的
index()方法也能找位置,但找不到会抛ValueError。用循环判断更可控——这也是为什么很多场景宁可手写循环也不用会抛错的方法。
题面:判断一个字符串是否为回文(正读反读一样,如"level"、"上海自来水来自海上")。忽略大小写和所有非字母数字字符。
思路点拨:先用循环或推导式过滤出字母数字字符并转小写,再判断它和它的反转是否相等。反转用切片 [::-1] 最方便。
参考骨架:
s = "A man, a plan, a canal: Panama" # TODO: 过滤出字母数字并转小写 cleaned = "".join(ch.lower() for ch in s if ch.isalnum()) print(f"是回文:{cleaned == cleaned[::-1]}") # True
⚠️ 常见坑:
isalnum()判断字母或数字,正好适合过滤标点和空格。别忘了转小写,否则 'A' 和 'a' 会被判不等。
多层嵌套的 if 有没有更舒服的写法? 嵌套超过两层就该警觉。两个立竿见影的手段:一是"提前返回"——把不满足条件的分支先处理掉,比如先判断输入是否合法,不合法就打印提示并进入下一轮循环,让主逻辑保持平坦;二是用 and/or 把相关条件合并成一个表达式。重构嵌套时有个心理障碍要跨过:很多人觉得嵌套"体现了思考的层次感",但读代码的人其实在为每一层缩进付注意力税,能摊平的都该摊平。
while True 加 break 是不是坏味道? 不一定,它是"次数未知循环"的地惯用形态,命令行交互、消息处理都是这个骨架。真正的问题是循环体太长——退出条件埋在五十行之后,读者要扫完全部才能确认"何时结束"。改进办法是把循环体抽成一个函数(第 2 章练习),让 while True 骨架短得一眼见底。
字符串的 find 和 index 有什么区别? 行为几乎一样,找不到时的表现不同:find 返回 -1,index 抛异常。选哪个取决于你把"找不到"当正常情况还是异常情况。统计词频时"某词不存在"很正常,用 find 或干脆 in 判断;解析固定格式文本时"标记不存在"意味着文件坏了,此时让 index 大声报错反而是帮忙。这个取舍思维在第 2 章异常处理一节会正式展开。
循环和字符串处理怎么配合才顺手? 给一个实用的组织模板:外层循环负责"逐个取元素",内层的三件事按固定顺序做——判断(这个元素要不要处理)、变换(转小写、去标点)、累积(拼结果或计数)。把这三步在心里编号,写任何文本处理题目都按这个顺序落笔,代码结构自然清晰。本节的文本清洗、回文判断两题都可以拆成这个三段式,做完回头对照一下。
题面:给定一个密码字符串,输出它的"遮罩版"(每个字符替换为星号,只保留长度信息),并做两条初判:长度不足 8 位提示"太短";包含空格提示"不能含空格"。
思路点拨:遮罩用乘法最省事——"*" * len(pwd),字符串乘整数是重复拼接。空格判断用 in。两个检查按顺序做,都通过再输出遮罩,逻辑是"先验证、后处理"的基本形态。
参考骨架:
pwd = "abc 12345" if len(pwd) < 8: print("太短") if " " in pwd: print("不能含空格") print("*" * len(pwd)) # 无论是谁都只看到星号
注意这里两个 if 是并列检查而不是互斥分支——两个问题可能同时存在,都该报出来。什么时候用 elif、什么时候并列用多个 if,判断依据就是"这些条件是否互斥"。
for 就别用 while:for 终止明确,不易死循环;while 适合次数未知的场景。break 跳出、continue 跳过:能用 if/else 表达就别用 continue,保持循环逻辑清晰。s[start:stop:step]:左闭右开,[::-1] 反转是最常用的技巧。split 与 join 互为反操作:文本与列表转换的高效工具。join 不用 +:循环里反复 + 会因频繁创建对象而变慢。下一章我们把代码组织成函数和模块——你已经有足够的语法原料,是时候学会复用了。