構(gòu)語句詳解:條件判斷與循環(huán)控制核心用法)
學(xué)Python有一段時間的人多半會卡在這樣一個坎上變量、數(shù)據(jù)類型、運算符都會了但拿到一個實際問題還是不知道代碼該怎么組織。原因很簡單——你只學(xué)了“零件”還沒學(xué)“骨架”。Python結(jié)構(gòu)語句就是程序的骨架它決定了代碼什么時候執(zhí)行哪一段、哪些操作要重復(fù)多少遍。這篇文章是Python系列教程的第三篇專門講條件判斷if / elif / else、循環(huán)遍歷for / while以及 break、continue、pass 這些控制關(guān)鍵字適合已經(jīng)掌握變量、列表、字典等基礎(chǔ)、準(zhǔn)備開始寫實際邏輯的讀者??赐曛竽銜涝趺醋尦绦颉白鲞x擇”和“重復(fù)勞動”也能避開新手最容易踩的縮進和死循環(huán)坑。1. 條件分支讓程序?qū)W會做選擇1.1 if / elif / else 的寫法和執(zhí)行順序條件分支是所有編程語言里最直白的骨架。Python 的寫法很固定關(guān)鍵字后面緊跟一個判斷表達式表達式末尾要加英文冒號冒號下面就是這個分支要執(zhí)行的代碼塊。代碼塊不需要像 C 語言那樣用大括號包起來Python 全靠縮進識別同一個分支里的語句縮進必須保持一致。score 85 if score 90: grade A elif score 80: grade B elif score 70: grade C else: grade D print(評級:, grade)這里有三條新手必須搞清楚的執(zhí)行規(guī)則。第一if / elif / else 從上往下逐個判斷只要有一個條件成立執(zhí)行完對應(yīng)的代碼塊之后后面的 elif 和 else 都不會再看。上面這個例子score 85 的時候第一個條件 85 90 不成立接著判斷 85 80 成立輸出 B后面兩個分支直接被跳過。第二elif 可以不寫else 也可以不寫但如果寫了 elif它前面必須先有一個 ifelse 永遠只能放在最后而且跟最近的 if 配對。第三冒號不能丟這是新手報 SyntaxError 最常見的原因之一。用編輯器寫代碼時如果某一行突然標(biāo)紅先低頭看上一行末尾有沒有冒號。嵌套的條件判斷在實際需求里很常見比如先判斷用戶角色再判斷這個角色有沒有對應(yīng)權(quán)限。嵌套本身沒問題但層數(shù)一多縮進越來越深可讀性直線下降。我自己的習(xí)慣是嵌套超過兩層就考慮把邏輯拆成獨立的小函數(shù)或者用“早返回”的結(jié)構(gòu)——先把不滿足條件的情況直接 return 掉主邏輯留在外層。寫復(fù)雜的數(shù)據(jù)處理腳本時這個習(xí)慣能幫你省大量排查時間。1.2 判斷表達式里的隱藏規(guī)則條件分支里放的判斷表達式很多人以為只能是 True / False 或者比較運算的結(jié)果其實 Python 里任何對象都可以直接放在 if 后面做判斷。這叫“真值測試”每個對象在布爾語境里都會被轉(zhuǎn)成 True 或 False。記住這條規(guī)律——以下對象會被當(dāng)作 FalseNone、數(shù)字 0包括 0.0、0j、空字符串 、空列表 []、空字典 {}、空元組 ()、空集合 set()。其他對象基本都是 True。values [0, 1, , hello, [], [0], None] for v in values: if v: print(repr(v), 為真) else: print(repr(v), 為假)運行結(jié)果會告訴你空列表為假、非空列表為真。這個特性非常實用判斷一個列表是否為空新手寫 len(items) 0老手直接寫 if items:。兩種都能跑但后者更簡潔也更符合 Python 社區(qū)的慣用風(fēng)格。判斷字符串是否為空用 if text:判斷字典里是否存在某個鍵則用 if key in data:。還有一個容易踩的坑是運算符優(yōu)先級。Python 里 not、and、or 的優(yōu)先級是從高到低也就是說 not 最先計算其次是 and最后是 or。寫 a or b and c 時實際計算的是 a or (b and c)不是 (a or b) and c。遇到這種表達我的建議永遠只有一個別省括號。給讀者看、給自己以后維護看加上括號都更省腦力。另外很多新手把比較運算和賦值搞混if score 85 是語法錯誤要寫成 if score 85。這個錯誤在筆試和日常 code review 里出現(xiàn)頻率都極高。Python 還支持連續(xù)比較a b c 等價于 a b and b c判斷數(shù)值范圍時很順手比如 0 score 100。三元表達式則適合做簡單的二選一賦值status 成年 if age 18 else 未成年。需要注意它本質(zhì)是表達式而不是語句只能用來產(chǎn)生值不能在里面寫復(fù)雜邏輯。如果三元的分支邏輯超過一行建議干脆寫完整的 if / else別為了省兩個字符犧牲可讀性。2. 循環(huán)讓代碼學(xué)會重復(fù)2.1 for 循環(huán)的正確姿勢for 循環(huán)是 Python 里使用頻率最高的結(jié)構(gòu)語句。它的設(shè)計哲學(xué)是“遍歷一切可迭代對象”列表、元組、字符串、字典、集合、文件對象都可以直接放在 in 后面?;緦懛╪ames [Alice, Bob, Charlie] for name in names: print(name)這里的 name 是循環(huán)變量每次循環(huán)自動從列表里取出一個元素賦給它。Python 的 for 不需要手動管理索引也不會出現(xiàn)“少循環(huán)一次”或者“數(shù)組越界”這類問題。想遍歷從 0 開始的數(shù)字序列用 range 函數(shù)for i in range(10): print(i) for i in range(1, 10, 2): print(i)range(stop) 生成 0 到 stop-1range(start, stop) 生成 start 到 stop-1range(start, stop, step) 帶步長。注意不管哪種寫法右邊界 stop 都是取不到的這是新手最容易犯的低級錯誤比如想輸出 1 到 100 卻寫成 range(1, 100)實際只到 99。想輸出 1 到 100 要寫 range(1, 101)。字典的遍歷也經(jīng)常用到。默認(rèn)遍歷字典拿到的是鍵同時要鍵和值用 items() 方法只要值用 values()。既想要序號又想要元素本身用 enumeratewords [apple, banana, cherry] for idx, word in enumerate(words): print(idx, word)enumerate 省去了自己維護計數(shù)器的麻煩默認(rèn)從 0 開始想從 1 開始就傳第二個參數(shù)enumerate(words, start1)。再講一個用的人不多但關(guān)鍵時候管用的東西for-else。Python 的 for 循環(huán)后面可以跟一個 else 子句它的語義是如果循環(huán)正常走完中途沒有被 break 打斷就執(zhí)行 else 里的代碼。最經(jīng)典的例子是判斷質(zhì)數(shù)num 7 for i in range(2, num): if num % i 0: print(num, 不是質(zhì)數(shù)) break else: print(num, 是質(zhì)數(shù))如果循環(huán)里發(fā)現(xiàn)了因子break 跳出else 不執(zhí)行如果 2 到 num-1 全試完都沒發(fā)現(xiàn)因子循環(huán)正常結(jié)束else 執(zhí)行輸出質(zhì)數(shù)。注意 else 和 for 是平級的縮進要和 for 對齊不是和 if 對齊。很多教材不講這個但關(guān)鍵時候它能讓“是否找到目標(biāo)”這類邏輯少寫一個標(biāo)志變量很值得掌握。2.2 while 循環(huán)與循環(huán)控制while 循環(huán)適合“不知道要循環(huán)多少次只知道滿足某個條件就繼續(xù)”的場景。寫法是 while 后面跟條件條件為真就一直執(zhí)行循環(huán)體count 0 while count 5: print(當(dāng)前計數(shù):, count) count 1這里 count 1 是循環(huán)能結(jié)束的關(guān)鍵。新手寫 while 最容易出的事故就是忘記在循環(huán)體里修改控制條件導(dǎo)致死循環(huán)。比如上面漏掉 count 1count 永遠是 0條件永遠為真程序就卡死了。在命令行里按 CtrlC 能中斷但如果你把這行代碼嵌進自動化腳本整個流程可能都會卡住排查時還很難定位。while 和 for 共用一套控制關(guān)鍵字break、continue、pass。break 是立刻結(jié)束整個循環(huán)不管后面還有多少輪直接跳出。continue 是跳過本輪剩余代碼進入下一輪。pass 是什么都不做的占位符通常用來補結(jié)構(gòu)??匆粋€典型的輸入校驗循環(huán)while True: num input(請輸入一個數(shù)字輸入 quit 退出: ) if num.lower() quit: print(再見) break if not num.isdigit(): print(輸入不合法請重新輸入) continue print(平方結(jié)果:, int(num) ** 2)這是“用戶輸入校驗”的標(biāo)準(zhǔn)模板。while True 構(gòu)造無限循環(huán)靠 break 退出輸入不合法時用 continue 回到輸入位置而不是繼續(xù)往下執(zhí)行。命令行交互工具、菜單選擇界面幾乎都長這樣務(wù)必掌握。break 和 continue 在嵌套循環(huán)里有一條特別容易誤解的規(guī)則它們只作用于自己所在的最近一層循環(huán)不會跳出外層。雙層循環(huán)里寫 break只是結(jié)束內(nèi)層循環(huán)外層繼續(xù)跑。這個規(guī)則用好很省事用錯很致命——排查半天也找不到為什么外層還在執(zhí)行。至于 pass最常見的用途是暫時空著函數(shù)體或類定義讓代碼先通過語法檢查后續(xù)再補實現(xiàn)。它本身的執(zhí)行效果就是什么都沒發(fā)生所以不要指望它有任何“跳過”作用那是 continue 的活。while 后面也能跟 else語義和 for-else 一樣循環(huán)沒被 break 打斷就執(zhí)行但用得比 for-else 還少知道有這回事即可。什么時候用 for、什么時候用 while我的經(jīng)驗是能明確知道要遍歷的對象或者循環(huán)次數(shù)預(yù)先確定就用 for只能靠條件判斷決定是否繼續(xù)、次數(shù)不確定尤其是和用戶輸入、網(wǎng)絡(luò)請求重試相關(guān)的場景就用 while。3. 把結(jié)構(gòu)語句串起來三個實戰(zhàn)場景3.1 數(shù)據(jù)篩選與統(tǒng)計if 加 for 的萬能組合理論講完看真實需求。假設(shè)有一組學(xué)生成績要把及格的挑出來再算平均分。這個需求用 if 加 for 組合解決scores [67, 89, 45, 92, 58, 73, 81] passed [] for s in scores: if s 60: passed.append(s) print(及格名單:, passed) print(平均分:, round(sum(scores) / len(scores), 2))這段代碼背后是“遍歷—判斷—收集”模式它能覆蓋大量數(shù)據(jù)處理場景過濾日志里的錯誤行、從商品列表挑出折扣商品、從傳感器讀數(shù)篩出異常值本質(zhì)上都是同一個骨架。Python 還提供了列表推導(dǎo)式來壓縮這種模式一行搞定passed [s for s in scores if s 60]執(zhí)行邏輯和上面的 for 循環(huán)完全等價只是更緊湊。我的個人建議是列表推導(dǎo)式一定要會寫會讀但剛切換過去時先在草稿紙上把它展開成 for 循環(huán)確認(rèn)邏輯。推導(dǎo)式里再套推導(dǎo)式或者里面塞了復(fù)雜條件分支還是老老實實用普通循環(huán)加 if可讀性永遠比“代碼短”重要。3.2 嵌套循環(huán)構(gòu)建鄰接矩陣第二個場景來自圖論和算法題也是搜索熱度很高的需求——構(gòu)建鄰接矩陣。假設(shè)圖有 n 個節(jié)點邊用元組列表表示要把邊關(guān)系寫進一個 n×n 的矩陣有邊的位置填 1沒邊的填 0。新手最容易寫出的錯誤版本是n 5 matrix [[0] * n] * n # 錯誤示范這個寫法看起來沒有任何問題實際上每一行都是同一個列表對象的引用。改 matrix[0][1] 1你會發(fā)現(xiàn) matrix[1][1]、matrix[2][1] 全都變成了 1因為所有行共享同一塊內(nèi)存。正確寫法是用列表推導(dǎo)式每一行都新建一個獨立的列表n 5 edges [(0, 1), (1, 2), (2, 3), (3, 4), (0, 4)] matrix [[0] * n for _ in range(n)] for u, v in edges: matrix[u][v] 1 matrix[v][u] 1 # 無向圖兩個方向都填 for row in matrix: print(row)這里 [[0] * n for _ in range(n)] 里的 _ 是慣例寫法表示“這個循環(huán)變量我不關(guān)心它的值”純粹用來控制生成 n 行。[0] * n 生成一行 n 個零推導(dǎo)式每跑一次就新建一行行與行互不干擾。這個坑我當(dāng)年實際踩過一次排查了很久才發(fā)現(xiàn)是“用乘法復(fù)制列表時共享了引用”。順便說這正是 Python 算法題里大量隱藏坑的來源凡是“用乘法創(chuàng)建嵌套列表”的操作都要警惕別名問題。如果算法題里遇到像“李白打酒”這類經(jīng)典問題本質(zhì)上也是循環(huán)結(jié)構(gòu)的練習(xí)。題目大意李白開始提著一定量的酒遇見酒店就翻倍遇見花就喝掉一斗三次遇店三次遇花之后酒剛好喝光問初始有多少酒。順著推不好下手倒過來用一個循環(huán)就清楚了wine 0 for _ in range(3): wine 1 # 逆著“見花喝一斗” wine / 2 # 逆著“遇店加一倍” print(wine) # 0.875循環(huán)把三次“遇店—遇花”的過程逆向執(zhí)行得到初始 0.875 斗酒。這個例子不求記住答案關(guān)鍵是體會“正向模擬困難時倒推往往能用一個簡單的循環(huán)解決”的思維方式。3.3 循環(huán)里處理異常輸入重試模板第三個場景是真實腳本繞不開的——和用戶交互時做異常處理。用 input 讀取數(shù)字時用戶可能輸入“abc”直接 int() 轉(zhuǎn)換就會拋出 ValueError 終止程序。用 while 加 try / except 做重試循環(huán)是標(biāo)準(zhǔn)解法while True: try: num float(input(請輸入一個數(shù)字: )) break except ValueError: print(輸入的不是合法數(shù)字請重新輸入) print(你輸入的是:, num)try 塊里只要成功完成轉(zhuǎn)換break 立刻退出循環(huán)轉(zhuǎn)換失敗則進入 except提示之后繼續(xù)下一輪循環(huán)?!皌ry-except while True break”這套組合我愿稱之為命令行腳本里最值得背下來的模板之一。它在讀取文件、請求接口、解析 JSON 等場景里同樣適用核心思想都一樣把可能出錯的步驟包進 try把重試的驅(qū)動力放在 while 上。4. 常見問題與排查實錄4.1 IndentationError縮進引發(fā)的血案縮進是 Python 和 C、Java 最大的區(qū)別之一也是新手報錯數(shù)量排行榜的榜首。IndentationError: unexpected indent 的意思是“出現(xiàn)了意外的縮進”常見原因有三個if / for / while 代碼塊內(nèi)部語句頂格寫了同一個代碼塊內(nèi)有的行用了 4 個空格、有的行用了 Tab復(fù)制粘貼時把不同編輯器的縮進風(fēng)格混在一起。解法很粗暴在編輯器里開啟“顯示空白字符”把 Tab 全部替換成空格然后重新整理縮進。用 PEP 8 的規(guī)定統(tǒng)一為 4 個空格是社區(qū)公認(rèn)的做法。VS Code 里按 CtrlShiftP輸入 Convert Indentation to Spaces一步搞定。另一個容易混淆的是邏輯縮進錯誤。Python 只檢查縮進層級是否一致不檢查你的縮進是否符合預(yù)期語義。比如if score 60: print(及格) print(這句也在 if 里) print(這句在 if 外面)第三行 print 因為縮進減少已經(jīng)不屬于 if 分支了。這種程序不會報錯但運行結(jié)果和預(yù)期完全不同排查的時候比 IndentationError 更隱蔽。我的經(jīng)驗是先看編輯器里的縮進輔助線再確認(rèn)目標(biāo)語句歸屬于哪個層級不要靠肉眼數(shù)空格。4.2 遍歷時刪除元素最常見的邏輯坑前面鄰接矩陣是“創(chuàng)建”時的別名坑這里還有一個“修改”時的迭代坑。在遍歷列表的同時刪除元素會因元素索引移位導(dǎo)致部分元素被跳過。看這個例子nums [1, 2, 4, 6, 8] for n in nums: if n % 2 0: nums.remove(n) print(nums) # 實際輸出 [1, 4]預(yù)期是刪掉所有偶數(shù)得到 [1]實際得到 [1, 4]因為 remove 改變了列表長度for 循環(huán)內(nèi)部的索引卻還在照常遞增4 被跳過去了。正確做法有三種遍歷列表的副本for n in nums[:]先記錄要刪的元素再統(tǒng)一刪除或者直接用列表推導(dǎo)式生成新列表。最推薦最后一種nums [1, 2, 4, 6, 8] nums [n for n in nums if n % 2 ! 0] print(nums) # [1]同樣的道理適用于字典——遍歷字典時往里加鍵或者刪鍵會直接報 RuntimeError: dictionary changed size during iteration因為字典的哈希表在迭代期間不允許結(jié)構(gòu)性修改。遇到這個錯就把要改的鍵先收集成列表遍歷結(jié)束后再統(tǒng)一操作。4.3 高頻易錯點速查表報錯或現(xiàn)象常見原因解決辦法SyntaxError: invalid syntaxif / for / while 行尾漏冒號、括號不配對檢查冒號用編輯器高亮檢查括號匹配IndentationError: unexpected indentTab 和空格混用、代碼塊縮進不統(tǒng)一統(tǒng)一為 4 個空格開啟顯示空白字符程序卡死、輸出刷屏while 條件恒為真循環(huán)體沒修改控制變量檢查循環(huán)體里的變量更新臨時加 print 觀察遍歷列表刪除元素結(jié)果不對邊遍歷邊 remove元素被跳過用列表推導(dǎo)式或遍歷 nums[:] 副本整數(shù)和浮點數(shù)比較總是 False浮點數(shù)二進制存儲誤差0.1 0.2 不等于 0.3用 abs(a - b) 1e-9 判斷相等或 round 后比較在函數(shù)內(nèi)給全局同名變量賦值報錯賦值讓該變量被解釋為局部變量需要修改全局變量時用 global 聲明循環(huán)次數(shù)少一次range 的右邊界取不到想包含 stop 值就寫成 range(開始, stop 1)for 后面的 else 沒執(zhí)行循環(huán)被 break 提前打斷檢查是否在某個條件下 break 提前退出了這張速查表里的問題我在幫朋友看代碼時幾乎每一類都見過。尤其是浮點數(shù)比較很多人第一次跑 0.1 0.2 0.3 得到 False 都會懵其實這是所有現(xiàn)代編程語言的通病不是 Python 的 bug。最后分享一點個人體會。結(jié)構(gòu)語句本身并不難難的是“什么時候用哪種”的判斷力。我自己的學(xué)習(xí)路徑是先把 if / elif / else、for、while 各寫十遍確保能默寫然后找一個綜合小項目比如命令行記賬本或者讀取 CSV 文件篩選數(shù)據(jù)把所有結(jié)構(gòu)串起來用最后才是刻意讀別人的代碼觀察別人在什么場景下用了 while True、什么場景下用了 for-else、哪里用了列表推導(dǎo)式。等到你寫代碼時不再需要停下來想“這里該用 if 還是 while”結(jié)構(gòu)語句就算是真正內(nèi)化了。