條件分支代碼方法)
前言「寫(xiě)條件分支」看起來(lái)是最沒(méi)技術(shù)含量的活但代碼質(zhì)量的分水嶺恰恰在這里。同一個(gè)業(yè)務(wù)規(guī)則有人寫(xiě)出五層嵌套的if有人用三行if/elif加一個(gè)字典就清爽收尾。區(qū)別不在于聰明而在于是否知道 Python 提供了哪些分支寫(xiě)法、各自適合什么場(chǎng)景。初學(xué)者最常見(jiàn)的三個(gè)誤解是以為and/or返回的是True/False其實(shí)返回的是參與運(yùn)算的操作數(shù)本身以為match就是 C 的switch其實(shí)沒(méi)有貫穿、還能匹配結(jié)構(gòu)以及以為x or default是安全的默認(rèn)值寫(xiě)法當(dāng)x是0或空字符串時(shí)會(huì)被錯(cuò)誤替換。本文按「從簡(jiǎn)單到復(fù)雜」的順序講六種條件分支寫(xiě)法并給出各自該在什么時(shí)候用。文中的match語(yǔ)句需要 Python 3.10 及以上其余寫(xiě)法在 Python 3.8 及以上都能跑。一、基礎(chǔ)if / elif / else最直接的寫(xiě)法。要點(diǎn)是命中即停所以判斷順序就是優(yōu)先級(jí)順序。# 適用于 Python 3 全版本def http_status_text(code):if code 200:return OKelif code 301:return Moved Permanentlyelif code 404:return Not Foundelif 500 code 600:return Server Errorelse:return Unknown這里用到了鏈?zhǔn)奖容^500 code 600它等價(jià)于500 code and code 600但更短也更不易寫(xiě)錯(cuò)。二、條件表達(dá)式三元運(yùn)算符當(dāng)分支只是為了「二選一取值」時(shí)用條件表達(dá)式比寫(xiě)四行if/else更清晰# 適用于 Python 3 全版本age 17label 成年 if age 18 else 未成年print(label) # 未成年形式是「真值 if 條件 else 假值」順序和 C 的條件 ? 真值 : 假值相反這是高頻寫(xiě)錯(cuò)點(diǎn)。它適合短表達(dá)式嵌套多層會(huì)立刻變得難讀# 適用于 Python 3 全版本# 不推薦嵌套三元可讀性差grade A if s 90 else (B if s 80 else C)多層判斷還是回到if/elif/else。三、布爾運(yùn)算的短路與返回值and和or有短路特性而且返回的是操作數(shù)本身a or ba為真就返回a否則返回ba and ba為假就返回a否則返回b# 適用于 Python 3 全版本print(0 or 默認(rèn)) # 默認(rèn)print(x or 默認(rèn)) # xprint(0 and 1 / 0) # 0右側(cè)未求值不會(huì)除零print(abc and 42) # 42利用返回值可以寫(xiě)出簡(jiǎn)短的默認(rèn)值邏輯但要注意假值判斷的陷阱or會(huì)把0、、[]也當(dāng)成「沒(méi)有值」。如果一個(gè)合法值就是0必須改用顯式的is None判斷# 適用于 Python 3 全版本def with_default(value, default100):# 錯(cuò)誤示范value 為 0 時(shí)也會(huì)被替換# return value or default# 正確只在確實(shí)是 None 時(shí)才用默認(rèn)值return default if value is None else valueprint(with_default(0)) # 0print(with_default(None)) # 100四、衛(wèi)語(yǔ)句提前返回消除嵌套深層嵌套的根因通常是「所有失敗情況都往里縮進(jìn)」。反過(guò)來(lái)寫(xiě)不合格的輸入提前 return把主邏輯留在最外層。# 適用于 Python 3 全版本# 不推薦嵌套三層def price_bad(user, item):if user is not None:if user.is_active:if item.stock 0:return item.priceelse:return -1else:return -2else:return -3# 推薦衛(wèi)語(yǔ)句提前返回def price_good(user, item):if user is None:return -3if not user.is_active:return -2if item.stock 0:return -1return item.price五、字典分派把「值相等」的分支換成查表當(dāng)分支條件是「某個(gè)值和一批常量逐個(gè)比較」時(shí)用字典把「動(dòng)作」或「結(jié)果」映射起來(lái)比一長(zhǎng)串elif更好維護(hù)。# 適用于 Python 3 全版本def apply_op(op, a, b):operations {: lambda x, y: x y,-: lambda x, y: x - y,*: lambda x, y: x * y,}func operations.get(op)if func is None:raise ValueError(f不支持的運(yùn)算符{op})return func(a, b)print(apply_op(*, 6, 7)) # 42注意用.get()而不是operations[op]直接下標(biāo)在沒(méi)有該鍵時(shí)會(huì)拋KeyError除非這正是你想要的錯(cuò)誤。字典分派的前提是分派鍵是可哈希且可比較的常量不適合范圍判斷。六、match 語(yǔ)句結(jié)構(gòu)化模式匹配Python 3.10從 Python 3.10 起語(yǔ)言引入了match/casePEP 634。它不是switch的翻版沒(méi)有貫穿fall-through命中一個(gè)case就結(jié)束能匹配結(jié)構(gòu)序列、映射、類(lèi)實(shí)例不只是值_是通配符相當(dāng)于默認(rèn)分支支持守衛(wèi)子句case ... if 條件:# 適用于 Python 3.10def describe(command):match command.split():case [go, direction]:return f向 {direction} 前進(jìn)case [look]:return 你環(huán)顧四周case [take, item] if len(item) 3:return f拿起一個(gè)較長(zhǎng)的物品{item}case [take, item]:return f拿起 {item}case _:return 無(wú)法識(shí)別的指令print(describe(go north)) # 向 north 前進(jìn)print(describe(take)) # 無(wú)法識(shí)別的指令需要在 Python 3.10 以下運(yùn)行時(shí)同一段邏輯只能用if/elif判斷l(xiāng)en(parts)和各位置的值來(lái)手寫(xiě)。實(shí)戰(zhàn)把六種寫(xiě)法用在一個(gè)「評(píng)分系統(tǒng)」里# 適用于 Python 3.10from dataclasses import dataclassdataclassclass Submission:author: strscore: int | None None # 3.10 的聯(lián)合類(lèi)型寫(xiě)法def grade_of(score):if score is None:return 未評(píng)分if score 90:return Aif score 80:return Bif score 60:return Creturn Ddef label(submission):if submission.score is None:return f{submission.author}待評(píng)分status 優(yōu)秀 if submission.score 90 else 已評(píng)分return f{submission.author}{grade_of(submission.score)}{status}def main():subs [Submission(Ada, 95),Submission(Bob, 72),Submission(Cy, None),]for s in subs:print(label(s))if __name__ __main__:main()輸出依次是AdaA優(yōu)秀、BobC已評(píng)分、Cy待評(píng)分。注意int | None這種聯(lián)合類(lèi)型寫(xiě)法需要 Python 3.10 及以上在 3.9 里要寫(xiě)typing.Optional[int]3.8 同理。常見(jiàn)坑點(diǎn)與混淆?if code 200:?if code 200:Python 3 里用在條件位置是語(yǔ)法錯(cuò)誤以為and/or返回布爾值? 用x and y的結(jié)果去做is True判斷 ? 它們返回操作數(shù)本身a and b得到b不是True用or提供默認(rèn)值把合法假值也替換掉?return value or default當(dāng)value是0//[]時(shí)誤用默認(rèn)值 ?return default if value is None else value把match當(dāng)switch用? 期待case會(huì)像 C 那樣向下貫穿或以為它只比常量 ?match無(wú)貫穿、能做結(jié)構(gòu)匹配、_是通配符需要 Python 3.10字典分派時(shí)直接用下標(biāo)導(dǎo)致KeyError?operations[op](a, b)? 先f(wàn)unc operations.get(op)判空后再調(diào)用或明確用 try/except 捕獲深層嵌套而不使用衛(wèi)語(yǔ)句? 把失敗分支一層層縮進(jìn)到看不見(jiàn)主邏輯 ? 不合格輸入提前return主邏輯留在最外層把范圍判斷寫(xiě)進(jìn)字典分派? 試圖用{range(0, 60): D, range(60, 80): C}直接查表 ?range對(duì)象相等比較不是區(qū)間匹配范圍判斷仍用if/elif或?qū)iT(mén)的區(qū)間表?xiàng)l件表達(dá)式順序?qū)懛?label 未成年 if age 18 else 成年? 真值在前l(fā)abel 成年 if age 18 else 未成年總結(jié)寫(xiě)法適用場(chǎng)景注意事項(xiàng)if/elif/else通用分支、范圍判斷命中即停順序即優(yōu)先級(jí)條件表達(dá)式二選一取值真值在前勿嵌套多層and/or短路簡(jiǎn)單默認(rèn)值、守衛(wèi)返回操作數(shù)假值會(huì)誤觸發(fā)衛(wèi)語(yǔ)句消除深層嵌套失敗提前返回字典分派常量到結(jié)果的映射用.get()防KeyErrormatch結(jié)構(gòu)匹配3.10無(wú)貫穿_為通配符判斷該用哪種寫(xiě)法只需要問(wèn)自己兩個(gè)問(wèn)題分支條件是「值相等」還是「范圍/結(jié)構(gòu)」值是常量就考慮字典分派是結(jié)構(gòu)就用match是范圍就用if/elif。至于嵌套過(guò)深先想到衛(wèi)語(yǔ)句而不是繼續(xù)往里縮進(jìn)。