ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python条件语句与循环语句:核心机制、避坑指南与实战优化

Python条件语句与循环语句:核心机制、避坑指南与实战优化 条件语句和循环语句在Python里是绕不开的两个基本功。很多人刚入门的时候觉得这俩语法太简单了if嘛判断一下for嘛遍历一下。但实际写起来你会发现各种莫名其妙的坑缩进不对报错、条件判断顺序写反了、循环遍历时修改列表结果全乱套。这篇文章我就把Python条件语句和循环语句掰开揉碎讲清楚包括它们背后的执行逻辑、容易被忽略的细节、以及我在实际项目里总结出来的一些替换方案和避坑经验。不管你是刚学Python的新手还是写了几年代码但想补补基础的老手这篇都能帮你把控制流这部分补扎实。1. 预处理认知条件与循环在Python知识体系里的位置1.1 控制流是整个编程思维的核心枢纽先别急着敲代码我们把视角拉高一点。任何程序本质上就是三件事顺序执行、条件分支、循环重复。顺序执行不用多说就是从第一行往下走条件分支就是用if决定“走哪条路”循环重复用for和while决定“这段逻辑要跑多少遍”。我辅导过不少新人发现一个普遍现象语法都会背但一到写实际需求就卡住。比如让你“统计一个列表里大于10的数字个数”他能写出来但是写法很笨——用while加下标手动遍历而不是用for直接迭代元素。这不是语法问题是思维问题。条件语句和循环语句不仅仅是“语法”它们是你把现实逻辑翻译成机器逻辑的工具。现实里的规则通常是“如果怎么样就怎么样”翻译过来就是if现实里的重复劳动翻译过来就是循环。1.2 为什么Python的条件和循环被单独拎出来讲很多编程语言里条件语句和循环语句是分开学的用法也各不相同。但Python有个特点它的语法极简、强制缩进这让条件与循环看起来“不像语法”更像是一种书写规范。但也正因为这样很多初学者容易忽略它们背后统一的执行逻辑。比如C语言里if后面必须跟括号for循环有个复杂的“初始化-判断-步进”三段式。Python全给简化了if后面冒号加缩进for直接遍历可迭代对象。这种简化是优点也是隐患。优点是你写起来快隐患是你并不知道底层在干什么一旦遇到复杂逻辑就抓瞎。所以这篇文章的定位不是给你念一遍文档而是帮你把这些基础语法用“项目实战思维”重新过一遍。我讲的每一个例子都不是为了演示语法而是为了解释“为什么这么写”“什么场景下用它”。这样你看完不是记住了几个代码片段而是真正具备用控制流解决问题的能力。2. 条件语句的深度拆解不止是if-else这么简单2.1 if-elif-else的基本结构与缩进铁律先看最基本的写法score 85 if score 90: grade 优秀 elif score 80: grade 良好 elif score 60: grade 及格 else: grade 不及格 print(grade)这个例子太经典了成绩判断。但我在这儿要强调一个新手最容易犯的错缩进就是Python的语法边界。在其他语言里缩进只是为了好看编译器不在乎但在Python里缩进直接决定了代码属于哪个块。你要么统一用4个空格要么统一用Tab不能混用。我见过太多人代码写着写着报IndentationError一查全是缩进混了。另外注意elif是else if的缩写不能写成else if这是Python和C、Java很不一样的地方。写else if在Python里是语法错误。整个条件判断是自上而下逐一执行的只要有一个条件满足后续的elif和else全都跳过。这就是“短路求值”在分支结构里的体现。这里有个容易被忽略的细节elif分支的顺序是有讲究的。比如上面的成绩判断如果先写score 60再写score 80那80分就会进“及格”而不是“良好”。因为第一个条件先命中后面的就直接跳过了。条件顺序写反是逻辑bug里最常见的一种回头我会在常见问题部分专门展开。2.2 Python真值判断条件语句里的隐藏暗坑很多人写条件判断只看布尔值但实际上Python的判断逻辑远不止True和False。在Python里以下对象在布尔语境下会被当作False常量False、None数值0、0.0空序列空字符串、[]空列表、()空元组、{}空字典、set()空集合其余的统统是True。这意味着你可以这么写user_input input(请输入内容: ) if user_input: print(你输入了:, user_input) else: print(你什么都没输入)if user_input这个写法比if user_input ! 更简洁、更Pythonic。同样判断列表是否为空直接if my_list:而不是if len(my_list) 0:。这个写法用得好代码会非常清爽用不好就是灾难。比如你从接口拿到一个字典直接用if data:判断有没有数据结果数据恰好是{count: 0}这种非空字典判断结果是True但这个字典里其实没有能用的业务数据。所以实际开发中真值判断适合判断“容器是否存在且非空”不适合判断“业务上的有效性”。业务上的有效请老老实实写具体的条件。2.3 三元表达式写简单分支时的偷懒利器如果你的分支只是做一个简单的赋值Python提供了一个简洁写法——条件表达式俗称三元表达式x 10 result 正数 if x 0 else 非正数 print(result)它的结构是值1 if 条件 else 值2。条件成立取值1不成立取值2。我建议只在“一句话能写清楚”的场景用三元表达式比如上面这种。如果逻辑复杂比如嵌套多个分支就不要硬用三元表达式嵌套的三元表达式可读性极差。我在Code Review时看到过类似a if x else b if y else c的写法说实话能看懂但每次都要在脑子里转一圈完全没有必要。2.4 match-case新版本Python带来的多分支新玩法Python 3.10加入了一个新语法match-case它的定位是结构化模式匹配。举个简单例子command start match command: case start: print(开始执行) case stop: print(停止执行) case _: print(未知命令)case _是兜底分支相当于其他语言里的default。match-case的优势是当你要根据一个变量的多个取值做不同操作时它的可读性比一串if-elif-else要好很多而且支持更复杂的模式匹配比如解包元组、绑定变量。不过我要提醒一下match-case虽然很酷但实际企业项目里用得并不算多。原因有两个一是很多存量代码跑在Python 3.8/3.9上用不了二是大部分场景用字典映射或if-elif就能解决。我的建议是了解这个语法知道有这个东西但不用刻意追求用它。基础扎实了遇到合适的场景自然能用上。2.5 条件组合用and、or、not写出更紧凑的判断多个条件同时判断时用逻辑运算符and、or、not组合。这一点很基础但我更想强调的是and和or的“短路求值”特性。def check_user(user_info): return user_info and user_info.get(name) and user_info.get(age, 0) 18这里的user_info如果是None整个表达式会直接返回None后面的代码根本不会执行。这就是短路的价值前面的条件为False则不再执行后面。你完全可以反过来利用这个特性写更紧凑的逻辑比如data get_data() if data is not None and data.get(status) ok: process(data)如果不利用短路求值你就得写嵌套的if先判断data is not None再判断data.get(status)。虽然结果一样但短路写法更扁平。不过要记住短路求值也意味着or如果前面为True后面的表达式不会执行。有时候你依赖后面的表达式做某些副作用比如打印日志结果它没执行排错就要花时间。条件判断这块核心就一句话明确你在判断什么、你的判断顺序是不是最优、你的真假关系有没有坑。把这三点想通了if的代码怎么写都顺。3. 循环语句的核心机制for到底在干什么3.1 for循环的本质是迭代而不是C语言那种计数循环很多从C或Java转过来的朋友写Python的for循环时总不习惯。因为我们习惯了三段式的for (i 0; i n; i)而Python的for完全不同——它直接遍历一个可迭代对象fruits [apple, banana, cherry] for fruit in fruits: print(fruit)这里fruit依次取列表里的每一个元素不需要下标不需要手动递增。把这个思维转过来是Python循环水平提升的第一步。你的关注点从“怎么控制循环次数”转移到“我要遍历什么数据”。但不是所有场景都能直接用元素遍历。有时候你就是需要下标。这时候用enumerate它同时给出下标和元素for index, fruit in enumerate(fruits): print(index, fruit)我记得有个新手朋友写过这样的代码for i in range(len(fruits)): print(fruits[i])这个写法不能算错但它多了一步“手动取下标再取元素”的操作。用enumerate就不用先range(len(...))再索引代码更清晰而且效率也不差。这就是我常说的“Pythonic”风格让代码表达意图而不是表达底层操作。3.2 range对象掌控循环次数的命脉range是Python循环里重要的辅助工具。它有三种常见用法range(n)生成0到n-1的整数序列range(start, stop)生成start到stop-1的整数序列range(start, stop, step)按步长step生成序列举个例子for i in range(1, 10, 2): print(i)打印结果是1、3、5、7、9。这里step2实现了“每两个取一个”。这种写法在“生成序列号”“每隔几个处理一下”的场景里非常实用。有个小知识点值得注意range是个惰性对象。在Python 3里range(1000000)并不会真的创建一个包含100万个整数的列表它只是记录起始值、结束值和步长每次迭代时现算现给。这对内存是极大的节省。所以放心大胆用大范围的range不要有心理负担。3.3 while循环什么时候用什么时候别用while循环的结构count 0 while count 5: print(count) count 1while适合什么场景适合循环次数事先不确定的场景。比如用户输入校验、读取文件直到末尾、等待某个状态变化。这些场景下迭代次数是靠运行时条件决定的写for反而不自然。经典例子是猜数字secret 42 guess 0 while guess ! secret: guess int(input(请猜一个数字: )) if guess secret: print(小了) elif guess secret: print(大了) print(恭喜猜对)这个循环什么时候结束不知道。可能三次猜对可能十次。你没法用for写一个固定次数的循环去覆盖这种场景所以while就派上用场了。但要警惕一个陷阱while循环如果忘记更新循环变量或者条件永远为真就成了死循环。上面的例子如果少了guess int(input(...))这一行程序就永远卡在循环里。写while第一原则确保循环体里有改变循环条件的语句。其实大部分数据遍历场景用for就足够了while更多用在“等条件成立”的场景。新人容易犯的错是把所有循环都写成while加手动控制既啰嗦又容易出错。我会建议能for就for需要等条件时再while。3.4 for-else和while-else一个被严重低估的隐藏语法这个语法我敢说至少三成Python程序员不知道循环也能配else。它的逻辑是循环正常结束没有被打断时执行else块。numbers [1, 2, 3, 4, 5] for num in numbers: if num 6: print(找到了6) break else: print(没有找到6)如果列表里有6循环会break跳出else不会执行如果找不到6循环完整跑完else执行。这个特性用来做“遍历查找后确认是否找到”非常合适比定义一个found标志变量要简洁得多found False for num in numbers: if num 6: found True break if found: print(找到了6) else: print(没有找到6)两段代码效果完全一样但用for-else少了一个标志位、少了一个if判断。这个语法尤其适合“搜索、校验、验证存在性”这类需求。不过我要提醒一句for-else的else很容易被人误读成“循环后执行”实际上它的语义是“循环未被break打断时执行”。这跟if-else的直觉完全不同。所以写代码时最好加上注释免得后来维护代码的人跟你纠结为什么else会执行。3.5 嵌套循环小心这是性能和时间复杂度的重灾区嵌套循环就是循环套循环。一个循环遍历外层的每一项内层循环对每一项再做完整遍历。经典例子是打印九九乘法表for i in range(1, 10): for j in range(1, i 1): print(f{j} x {i} {i * j}, end\t) print()这个不难理解但我要说的是复杂度问题。外层n次内层m次总次数就是n乘以m。如果n和m都是上千的数量级那就是百万次操作如果套三层那就是十亿次。这可不是开玩笑的。我在代码评审时经常看到三层嵌套循环处理数据每层几百上千的量级实际跑起来等半天。这时候正确的做法往往是把内层循环改造成字典映射用O(1)的查找替代O(n)的遍历。比如你拿到两个列表一个存用户ID一个存订单的用户ID想统计每个用户有多少订单。新手会写双重循环users [1, 2, 3, 4, 5] orders [1, 1, 2, 3, 3, 3, 5, 5] result {} for user in users: count 0 for order in orders: if order user: count 1 result[user] count这个写法逻辑正确但效率很差。如果用户有1万订单有100万就要跑100亿次比较。实际上你只要先遍历一次订单把订单按用户ID分组计数再遍历用户去查复杂度直接从O(n*m)降到O(nm)from collections import Counter users [1, 2, 3, 4, 5] orders [1, 1, 2, 3, 3, 3, 5, 5] order_count Counter(orders) result {user: order_count.get(user, 0) for user in users}这个思路的本质是能用字典解决的问题绝不要用嵌套循环硬扛。嵌套循环不是不能写而是你要清楚它背后的性能代价并且在数据量大时主动找替代方案。这是从“会写循环”到“能用好循环”的关键跨越。4. 控制流中的关键兵器break、continue、pass4.1 三个控制语句的区别与适用场景循环体里有三张王牌break、continue、pass。break立即终止整个循环后面的代码不再执行continue跳过本次迭代剩下的代码直接进入下一次迭代pass什么都不做只是占位看个例子for i in range(1, 10): if i 3: continue if i 7: break print(i)打印结果是1、2、4、5、6。遇到3时continue跳过本次循环所以3没打印遇到7时break直接终止循环所以7、8、9都没打印。pass最典型的用途是搭骨架。你定义一个函数或者一个分支暂时还没想好实现但语法上必须要有代码块def process_data(): pass # 待实现如果你不写pass这个函数体就是空的Python直接报语法错误。pass的角色就是“我先占个位置后面再来填”。4.2 break和continue嵌套时只作用于最近的一层循环这个细节我在实际代码里见过太多次错误了。break和continue只作用于离它最近的那层循环。看这个两层循环的例子for i in range(3): for j in range(3): if j 1: break print(i, j)输出是0 0 1 0 2 0内层循环遇到j 1时break只跳出了内层循环外层循环继续。所以外层还会继续取i 1、i 2。如果你想让break直接跳出所有循环怎么办常见方案是设置一个标志变量flag False for i in range(3): if flag: break for j in range(3): if j 1: flag True break print(i, j)或者用我之前讲的for-else外层循环结束时检查内层是否被打断。不过这些写法都有点绕。说实话嵌套循环里想“一层break跳出所有”的场景往往意味着代码结构可以重构比如把逻辑抽成函数用return代替多层break。这是更优雅的解法。4.3 实战用条件与循环写一个经典的小功能说了这么多理论我们来揉一个综合一点的小需求判断一个数是不是素数。def is_prime(n): if n 2: return False for i in range(2, int(n ** 0.5) 1): if n % i 0: return False return True for num in range(2, 50): if is_prime(num): print(num, end )这里有两个要点。第一判断素数的循环只需要跑到n的平方根就够了不用跑到n本身。因为如果一个数有大于平方根的因子必然对应一个小于平方根的因子你检查小的那头就够了。这是时间复杂度从O(n)降到O(√n)的关键优化。第二range(2, int(n ** 0.5) 1)这个写法要注意range的结束位置是开区间所以要用 1确保平方根本身被包含到检查范围内。如果n是49int(7) 1 8range(2, 8)包含2到7刚好覆盖。在这个函数里我在循环内用了return False直接跳出整个函数。这其实比break更干净——反正函数不需要再往下执行了不如直接返回结果。4.4 循环与数据结构的配合从遍历到统计循环真正的威力在于跟数据结构结合。比如拿到一个销售数据列表要统计各品类销量。你当然可以手写循环加字典sales [ {category: 水果, amount: 10}, {category: 零食, amount: 20}, {category: 水果, amount: 15}, {category: 饮料, amount: 8}, ] result {} for sale in sales: cate sale[category] if cate not in result: result[cate] 0 result[cate] sale[amount] print(result)这里面if cate not in result是典型的“先检查再操作”模式循环里每一行都是必要的。但用Python标准库collections.defaultdict会更简洁from collections import defaultdict result defaultdict(int) for sale in sales: result[sale[category]] sale[amount]defaultdict(int)保证了当键不存在时主动创建并赋值为0省掉那两行检查代码。这个写法的背后逻辑是循环体越简洁出错的概率越低代码的可读性越高。我在实际项目中统计几乎全用defaultdict和Counter手写循环加if判断的场景少之又少。5. 常见错误排查我踩过的条件与循环的那些坑5.1 悬挂else条件分支顺序导致的隐性bug什么叫悬挂else就是else不知道该跟哪个if配对。Python的语法规则是else跟最近的、未配对的if配对。因为Python用缩进表示代码层级“悬挂”问题比C语言少很多但仍有踩坑余地。比如这段代码逻辑上想表达“分数大于等于80且小于90”score 85 if score 90: grade 优秀 elif score 60: if score 80: grade 良好 else: grade 及格 else: grade 不及格缩进没问题时这段代码能正确运行。但一旦缩进乱掉比如else跟外层if对齐了if score 90: grade 优秀 elif score 60: if score 80: grade 良好 else: grade 不及格那么当score85时score 60成立进入第二个分支内部的if score 80成立grade良好没问题。但如果score65内部if不成立直接什么都不做grade根本没定义因为那个else现在跟外层elif配对了而外层条件已经成立进入分支了不会再走else。排查这类bug我的经验是先通过缩进判断每个else、elif的配对关系再顺着业务逻辑走一遍。新手往往看到缩进是对的就觉得逻辑对忽略了分支间的配对关系。更稳妥的办法是把嵌套if拆成扁平的多条件组合if score 90: grade 优秀 elif 80 score 90: grade 良好 elif 60 score 80: grade 及格 else: grade 不及格这样每个分支的条件都是独立的谁跟谁配对一目了然再也不会出现“条件没涵盖到的漏网之鱼”。5.2 循环里修改正在遍历的列表这个坑我见得太频繁了在for循环里直接删除、添加正在遍历的列表元素结果循环次数全乱。nums [1, 2, 3, 4, 5, 6] for num in nums: if num % 2 0: nums.remove(num) print(nums)你以为结果是[1, 3, 5]但实际可能不对。因为当nums变成[1, 2, 3, 4, 5, 6]开始遍历第一次迭代num1不删除第二次num2被删除列表变成[1, 3, 4, 5, 6]但迭代器的指针已经指向了下一个位置也就是索引2现在变成了4——3被跳过了。遍历过程中修改列表会让迭代器的行为变得不可预测。正确做法有两种一是遍历副本修改原列表nums [1, 2, 3, 4, 5, 6] for num in nums[:]: if num % 2 0: nums.remove(num)二是用列表推导式生成新列表nums [num for num in nums if num % 2 ! 0]我强烈推荐第二种。它不修改原列表直接生成一个新列表逻辑清晰效率也高。记住一条铁律循环遍历时绝不改正在遍历的容器。要么遍历副本要么生成新容器。5.3 死循环与“差一”错误死循环的排查相对好解决加打印语句看循环变量变化就行。真正阴险的是“差一”off-by-one错误循环次数差了一个。最容易出现差一错误的两个地方。第一是range的结束位置不计入range(1, 10)产生1到9没有10。第二是while条件的边界到底是还是。我举一个典型差一错误的场景def binary_search(arr, target): left, right 0, len(arr) - 1 while left right: mid (left right) // 2 if arr[mid] target: return mid elif arr[mid] target: left mid 1 else: right mid - 1 return left if arr[left] target else -1这里while left right意味着当left和right相等时循环结束。如果目标正好在left和right相撞的那个位置你需要在循环结束后再补一个判断。哪个用哪个用完全取决于你的语义边界。我的经验是写循环时先在纸上走一遍边界情况——空列表、单元素列表、目标在第一个、目标在最后一个这四个用例过了基本上差一错误就能被挡住。5.4 误用break/continue导致逻辑混乱break和continue用错位置程序也能跑但结果就是不对。我遇到过一个真实案例业务方要求在解析数据时跳过错误记录结果continue写在了外层循环导致一整批数据全被跳过线上差点事故。当时的代码结构大概是这样的for batch in batches: # 外层批次 for record in batch: # 内层记录 if not validate(record): continue # 本意只跳过这条记录 process(record) # 处理后记录 upload(batch) # 上传批次看起来没问题continue确实只作用于内层。如果错误出现在缩进上比如continue写到了外层循环体里那一个批次一条脏数据就会导致整个批次被跳过。这个坑的排查方法很简单在break和continue处打日志打印当前处于哪一层循环。实话说遇到诡异逻辑打印信息永远是最快的排错手段别猜。5.5 条件判断里的“挂在嘴边”的坑写条件判断时还有两个常见坏习惯。第一把赋值当成相等写在if里。Python直接不支持这种写法是语法错误这个反而好了。第二忽略运算符优先级。条件表达式混用and、or、not时not优先于andand优先于or。新手容易读错尤其是这种if a and b or c:这个意思是(a and b) or c。如果你想表达的是a and (b or c)结果完全不同。我建议复杂的条件判断一律加括号不要依赖优先级默认值。代码是给人读的不是让解释器炫技的。6. 提升循环代码质量的几个硬核技巧6.1 列表推导式用表达式替代简单循环如果你只想把循环结果收集成一个列表别写for加append了用列表推导式# 生硬写法 result [] for i in range(10): if i % 2 0: result.append(i * i) # 推导式 result [i * i for i in range(10) if i % 2 0]推导式的结构是表达式、循环、条件。它把“遍历-过滤-变形-收集”四件事压缩在一行里执行效率也比手写循环加append更高因为列表推导式在底层有优化。同样还有字典推导式、集合推导式square_dict {x: x * x for x in range(5)} even_set {x for x in range(10) if x % 2 0}推导式是简化循环最有力的武器。它不光是写法上的简化更是一种思维上的转变从“一步步往列表里加”变成“声明这个列表是什么”。前者是命令式的过程后者是声明式的结果。6.2 用all、any、enumerate、zip让循环退出舞台很多循环实际上是在做“检查”和“配对”的活儿这些用内置函数更合适。all和any用来检查序列里的元素是否全部/任意满足条件ages [18, 22, 25, 17] all_adult all(age 18 for age in ages) has_child any(age 18 for age in ages)配合生成器表达式连列表都不用建省内存。zip用来并行遍历多个可迭代对象names [张三, 李四, 王五] scores [88, 92, 76] for name, score in zip(names, scores): print(name, score)在Python 3.10里还有itertools.zip_longest可以处理长度不等的序列。6.3 用字典替代多个elif消除超长条件链没有match-case的Python版本里一串if-elif-else往往可以用字典映射替代。比如根据操作类型执行不同函数def add(a, b): return a b def sub(a, b): return a - b def mul(a, b): return a * b def div(a, b): return a / b operations { : add, -: sub, *: mul, /: div, } def calc(op, a, b): func operations.get(op) if func: return func(a, b) raise ValueError(未知运算符)这个写法有两个好处。第一分支不再是if-elif的线性链条而是直接通过键查找跳转到对应函数速度更快尤其分支很多时。第二扩展性极好。新增一个运算符只需要在字典里加一个键值对不用改函数体。这就是“表驱动编程”——把逻辑和数据分离代码会清爽很多。6.4 循环性能优化能少跑一次就少跑一次循环里的代码每多执行一次性能就多一分损耗。三个优化思路第一把循环里不变的表达式提到循环外。比如# 慢每次循环都计算一次len for i in range(len(data)): print(data[i]) # 快先算好长度 n len(data) for i in range(n): print(data[i])在数据量大时这种微优化效果明显。Python的len(data)本身很廉价但如果有更复杂的计算比如len(get_all_items())每次都调用函数就浪费了。第二用局部变量替代全局变量。循环里访问局部变量比访问全局变量快因为Python按作用域查找时局部变量在最前面。实战中如果循环里大量访问某个全局变量可以先用局部变量接收local_data global_data for item in items: process(local_data, item)第三用itertools处理复杂的迭代需求。itertools是Python标准库里的迭代神器product做笛卡尔积、permutations做排列、groupby做分组很多原本要写多重循环的场景用它们一行搞定。不过这是进阶内容了新手可以先知道有这个东西需要时再去查。6.5 代码可读性的自我修养写循环和条件除了正确和高效还有一个重要维度别人能不能看懂。如果条件太复杂拆成有意义的变量# 低可读性 if user.age 18 and user.vip_level 2 and user.status active and user.area in [华东, 华南]: send_coupon(user)# 高可读性 is_adult user.age 18 is_vip user.vip_level 2 is_active user.status active in_target_area user.area in [华东, 华南] if is_adult and is_vip and is_active and in_target_area: send_coupon(user)用有语义的变量名替代散落的判断条件读起来就像读一段自然语言。我见过太多人代码功能完全正确但因为到处是魔法数字、玄学条件维护时一个礼拜后连自己都不知道当时为什么那么写。控制流是最能体现一个程序员代码品味的地方。7. 实操场景演练把条件和循环串成一个完整小项目7.1 需求写一个简易的成绩统计系统理论讲再多不如自己动手跑一遍。我们来设计一个完整的控制流练习给定一个学生成绩字典要求输出及格率、平均分、最高分并且按分数段统计人数。scores { 张三: 92, 李四: 58, 王五: 75, 赵六: 88, 孙七: 63, 周八: 45, } total 0 passed 0 max_score -1 level_count {优秀: 0, 良好: 0, 及格: 0, 不及格: 0} for name, score in scores.items(): total score if score 60: passed 1 if score max_score: max_score score if score 90: level_count[优秀] 1 elif score 80: level_count[良好] 1 elif score 60: level_count[及格] 1 else: level_count[不及格] 1 avg total / len(scores) pass_rate passed / len(scores) * 100 print(f平均分: {avg:.1f}) print(f及格率: {pass_rate:.1f}%) print(f最高分: {max_score}) print(f分数段统计: {level_count})这一个例子同时覆盖了for遍历字典、if-elif-else多分支、累加统计、最大值更新。跑一遍输出非常直观。如果你想更深入练习可以加一个需求按分数从高到低排名。ranked sorted(scores.items(), keylambda item: item[1], reverseTrue) for rank, (name, score) in enumerate(ranked, start1): print(f第{rank}名{name}{score}分)sorted配合key参数实现排序enumerate从1开始编号排名reverseTrue从高到低。这一套组合拳下来你已经不是在背语法了而是在用语法解决问题。7.2 需求实现一个简易的菜单循环系统再来看一个while循环的经典应用命令行菜单。它需要不断接受用户输入直到用户选择退出。def show_menu(): print(1. 查看余额) print(2. 存款) print(3. 取款) print(4. 退出) balance 1000 while True: show_menu() choice input(请选择操作: ) if choice 1: print(f当前余额: {balance}) elif choice 2: amount float(input(请输入存款金额: )) if amount 0: balance amount print(f存款成功当前余额: {balance}) else: print(存款金额必须大于0) elif choice 3: amount float(input(请输入取款金额: )) if 0 amount balance: balance - amount print(f取款成功当前余额: {balance}) else: print(取款金额无效或余额不足) elif choice 4: print(感谢使用再见) break else: print(无效输入请重新选择)while True配合break是构建交互式程序的标准模式——程序一直跑直到用户明确要求退出。注意这里我把输入校验放在了业务逻辑里存款金额大于0才执行取款金额既不能小于等于0也不能大于余额。这种“先验证再操作”的思路在真实业务系统里比“出错了再补救”要可靠得多。控制流的逻辑本质就是把“条件”和“动作”正确配对这里每一项都是。7.3 需求从文件读取数据并筛选出有效行最后来个更实战一点的读取一个日志文件筛选出包含“ERROR”的行并统计各类错误数量。from collections import Counter error_counter Counter() with open(app.log, r, encodingutf-8) as f: for line in f: if ERROR in line: print(line.strip()) # 假设日志格式为: [时间] ERROR 模块: 错误描述 parts line.split() if len(parts) 4: module parts[2].rstrip(:) error_counter[module] 1 print(错误统计) for module, count in error_counter.most_common(): print(f{module}: {count}次)这个例子体现了循环在生产环境里的真实用法逐行读取、逐行判断、逐行处理。with open会自动管理文件关闭for line in f直接迭代文件对象而不需要一次性读入内存处理大文件时内存占用非常低。Counter负责统计most_common()按出现次数降序排列。整个过程没有一个多余的中间步骤这就是理想状态下的循环代码。8. 最后的提速心得如何训练你的控制流思维这篇文章的内容不算少但写到这里我想再跟你分享一个实际带新人时反复强调的方法。很多人学条件语句和循环语句习惯于“看懂了”就觉得自己会了。这是最大的误区。看懂了和写出来了之间隔着一道巨大的鸿沟。我给你三个可落地的训练方法。第一个方法把现实生活中的决策场景翻译成代码。比如地铁刷卡进站判断余额是否够、闸机是否开放、是否超时未出站。这些判断逻辑就是一串条件分支。平时遇到这类场景在脑子里走一遍怎么写比刷十道题都管用。没必要运行也不用来回改细节就是训练条件分支的敏锐度。第二个方法手写循环。别看代码拿张纸写一个循环模拟它的执行过程。追踪每一轮循环变量的值、容器内容的变化、最终输出的结果。不要用电脑就跑脑子里。刚开始觉得慢但练多了你会发现很多“运行结果想不通”的诡异bug其实就是因为你没养成模拟执行的习惯。第三个方法刻意重构。把你之前写过的、充满重复代码的功能用控制流和数据结构重新写一遍。比如之前用五个if判断的改成elif之前用了三层嵌套循环的看看能不能用字典压成一到两层之前用while True硬撑的想想是不是可以改进循环条件。重构是最快提升代码能力的方式没有之一。我一开始学Python的时候也觉得条件语句不难、循环语句不难。但真正开始做项目后才发现程序的复杂度极少来自某个语法点本身而是来自判断与判断的组合、循环与循环的嵌套、以及它们跟数据结构的相互配合。把这篇文章里的这些细节吃透你会发现自己写代码的时候心里有底了——不再是被动地试错而是主动地编排逻辑。这大概就是控制流给程序员带来的最宝贵的思维方式。
返回列表