則解析:invalid-legacy-positional-parameter 與 Python 位置參數(shù)的兩代約定)
Ruff ty 類型檢查器規(guī)則解析invalid-legacy-positional-parameter 與 Python 位置參數(shù)的兩代約定【免費(fèi)下載鏈接】ruffAn extremely fast Python linter and code formatter, written in Rust.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ru/ruff本篇技術(shù)指南圍繞 Ruff 倉庫中 ty 類型檢查器位于crates/ty_python_semantic等 crate入口參見 crates/ty/README.md的invalid-legacy-positional-parameter規(guī)則展開說明它為何檢測以__雙下劃線開頭卻無法被類型檢查器識別為僅限位置positional-only參數(shù)的寫法并從 PEP 484 遺留約定、PEP 570 正式語法到該規(guī)則的源碼實(shí)現(xiàn)層層拆解。讀完本文你將理解這條 lint 的觸發(fā)條件與邊界掌握修正舊式位置參數(shù)寫法的兩種方案并能定位其檢查邏輯、AST 判定函數(shù)與簽名構(gòu)建流程的源碼位置在實(shí)際項(xiàng)目中準(zhǔn)確復(fù)現(xiàn)與規(guī)避該診斷。規(guī)則定位它在檢查什么該規(guī)則的官方文檔位于 crates/ty_python_semantic/resources/lint_docs/invalid-legacy-positional-parameter.md語義描述為檢查那些試圖使用遺留約定legacy convention聲明某個(gè)參數(shù)為 positional-only但寫法并不正確the parameter appears right after a normal positional-or-keyword parameter的情況。圍繞這條規(guī)則有兩代僅限位置參數(shù)的規(guī)范需要厘清PEP 484 遺留約定legacy conventionPEP 484 規(guī)定對于名稱以__開頭且不以__結(jié)尾的參數(shù)類型檢查器應(yīng)當(dāng)將其視為 positional-only。這套約定只對靜態(tài)類型檢查器生效Python 運(yùn)行時(shí)并不會因此限制調(diào)用方式。PEP 570 正式語法Python 3.8在函數(shù)參數(shù)列表中使用/分隔符顯式聲明其左側(cè)的所有參數(shù)為 positional-only。該語法自 Python 3.8 起可用使 PEP 484 的__命名約定變得過時(shí)。正因如此仍有一些代碼庫為了兼容 Python 3.7 及更早版本而繼續(xù)沿用__命名約定——這正是invalid-legacy-positional-parameter規(guī)則存在的意義它不是要禁用遺留約定而是要攔截錯(cuò)誤地使用遺留約定的情況。為什么這種寫法是錯(cuò)的按 PEP 484 的約定類型檢查器只會把處于參數(shù)列表最前面一段連續(xù)位置即所有 positional-or-keyword 參數(shù)之前的__前綴參數(shù)視為 positional-only。一旦某個(gè)__前綴參數(shù)出現(xiàn)在一個(gè)普通的 positional-or-keyword 參數(shù)之后類型檢查器就不會再把它當(dāng)作 positional-only而只會當(dāng)它是一個(gè)以__開頭的普通參數(shù)。這往往與代碼作者的預(yù)期相悖——作者以為已經(jīng)借命名約定了禁用關(guān)鍵字傳參實(shí)際并沒有。觸發(fā)示例與兩種修復(fù)方案規(guī)則文檔給出了最典型的錯(cuò)誤樣例# __y 不會被類型檢查器視為 positional-only def f(x, __y): # error pass由于x是一個(gè) positional-or-keyword 參數(shù)且排在__y之前類型檢查器無法把__y歸入前導(dǎo) positional-only 段于是觸發(fā)本規(guī)則。修正方法有兩條路徑取決于你愿意支持的最低 Python 版本方案一讓所有 positional-only 參數(shù)連續(xù)排在最前兼容 Python 3.7def f(__x, __y): # 需要兼容 Python 3.7 時(shí)使用 pass把x也改名為__x使__前綴參數(shù)形成從參數(shù)列表開頭連續(xù)的一段滿足 PEP 484 遺留約定對前導(dǎo)位置的要求。方案二升級到 Python 3.8 的顯式/語法def f(x, y, /): # Python 3.8 語法 pass/之前的x、y均被顯式聲明為 positional-only。這條路徑不依賴任何命名技巧語義最清晰也是現(xiàn)代代碼的首選。實(shí)踐中的邊界情況從源碼實(shí)現(xiàn)可以確認(rèn)該規(guī)則還有幾個(gè)值得注意的邊界已使用 PEP 570 語法時(shí)不再做遺留約定檢查檢查函數(shù)會先判斷 AST 參數(shù)列表中是否已存在/聲明的 positional-only 參數(shù)posonlyargs若存在則整個(gè)跳過本次檢查見下文源碼解析避免新舊語法混用場景下產(chǎn)生噪聲。真正的 dunder 名稱不會被誤傷規(guī)則命中的條件是名稱以__開頭且不以__結(jié)尾因此__init__、__call__這類 dunder 方法參數(shù)不會被當(dāng)作遺留約定的使用者。方法隱式收參self/cls會先占位在方法簽名構(gòu)建時(shí)隱式收參會先被排入 positional-only 段見下文簽名構(gòu)建邏輯其后緊跟的__前綴參數(shù)仍可被正確識別。源碼級解讀規(guī)則如何落地下面沿規(guī)則聲明 → 函數(shù)入口 → 判定邏輯 → AST 判定方法 → 簽名構(gòu)建這條鏈?zhǔn)崂韺?shí)現(xiàn)所有路徑均以當(dāng)前倉庫為準(zhǔn)。規(guī)則聲明默認(rèn)級別與穩(wěn)定版本規(guī)則通過declare_lint!宏聲明于 crates/ty_python_semantic/src/types/diagnostic.rssummarydetects incorrect usage of the legacy convention for specifying positional-only parametersdefault_levelWarn默認(rèn)警告級不作為錯(cuò)誤阻斷statusstable(0.0.15)即自 0.0.15 版本起穩(wěn)定可用同一規(guī)則也登記于 crates/ty/docs/rules.md 的規(guī)則總表中該 lint 在運(yùn)行時(shí)通過registry.register_lint(INVALID_LEGACY_POSITIONAL_PARAMETER)注冊見 diagnostic.rs。檢查入口與觸發(fā)點(diǎn)函數(shù)定義的各類診斷在check_function_definition中統(tǒng)一調(diào)度其中就包含對遺留約定的專項(xiàng)檢查調(diào)用見 post_inference/function.rs。需要說明的是進(jìn)入該函數(shù)之前帶有不進(jìn)行類型檢查類裝飾器no_type_check的函數(shù)定義會被提前返回從而整體跳過包括本規(guī)則在內(nèi)的一批函數(shù)級診斷見 function.rs。核心判定函數(shù)判定主體為check_legacy_positional_only_convention位于 post_inference/function.rs。其邏輯要點(diǎn)如下前置過濾若ast_parameters.posonlyargs非空即函數(shù)已顯式使用 PEP 570/語法直接返回不做遺留約定檢查。配對遍歷將 AST 參數(shù)節(jié)點(diǎn)與解析后的簽名參數(shù)signature.parameters()按位置一一配對逐一跳過變長參數(shù)*args等。雙階段識別對某個(gè)__前綴參數(shù)而言合法的遺留約定用法會在簽名構(gòu)建階段被識別并標(biāo)記為 positional-only從而在這里被continue跳過只有那些沒有被識別為 positional-only、卻仍然以__開頭即uses_pep_484_positional_only_convention()返回 true的參數(shù)才會走到報(bào)告邏輯——這正好印證了文檔所述排在 positional-or-keyword 參數(shù)之后的__參數(shù)是無效遺留約定。診斷信息命中時(shí)報(bào)告主注解消息Parameter name begins with__but will not be treated as positional-only附加說明infoA parameter can only be positional-only if it precedes all positional-or-keyword parameters若存在更早出現(xiàn)的普通參數(shù)則在其名稱上追加輔助注解Prior parameter here was positional-or-keyword幫助用戶一眼定位破壞前導(dǎo)連續(xù)性的那個(gè)參數(shù)。同時(shí)函數(shù)維護(hù)previous_non_positional_only游標(biāo)遇到第一個(gè)非 positional-only 參數(shù)后就不再更新用于給后續(xù)所有無效__參數(shù)提供參照物注解。AST 層的判定方法是否是 PEP 484 遺留約定的寫法這一判斷被封裝在 AST 節(jié)點(diǎn)的工具方法中見 crates/ruff_python_ast/src/nodes.rs/// Return true if the parameter name uses the pre-PEP-570 convention /// (specified in PEP 484) to indicate to a type checker that it should be treated /// as positional-only. pub fn uses_pep_484_positional_only_convention(self) - bool { let name self.name(); name.starts_with(__) !name.ends_with(__) }即__foo、__bar命中__init__、__self__這類 dunder 因?yàn)橐訽_結(jié)尾而被排除。簽名構(gòu)建為何前導(dǎo)連續(xù)才有效若要真正理解為什么__y跟在x后面就失效需要看簽名構(gòu)建階段的處理見 crates/ty_python_semantic/src/types/signatures.rs首先收集 PEP 570 顯式聲明的 positional-only 參數(shù)posonlyargs僅當(dāng)顯式 positional-only 參數(shù)為空時(shí)才回退去識別 PEP 484 遺留約定若函數(shù)存在隱式位置收參如方法中的self/cls對應(yīng)has_implicitly_positional_first_parameter先把它作為 positional-only 放入隨后用peeking_take_while從頭連續(xù)地吞下所有uses_pep_484_positional_only_convention()為真的參數(shù)并把它們標(biāo)記為ParameterKind::PositionalOnlytake_while意味著一旦遇到第一個(gè)不符合約定的參數(shù)后續(xù)即使再出現(xiàn)__前綴參數(shù)也不會再被吞入它們將落入普通的PositionalOrKeyword段——于是排在中途的__參數(shù)就必然成為invalid-legacy-positional-parameter的靶子。這條實(shí)現(xiàn)鏈條清晰印證了規(guī)則文檔的核心結(jié)論legacy 約定下一個(gè)參數(shù)要成為 positional-only必須位于所有 positional-or-keyword 參數(shù)之前。如何運(yùn)行與驗(yàn)證該類型檢查器在倉庫內(nèi)以獨(dú)立的tybin 形式提供可通過 Cargo 直接運(yùn)行見 crates/ty/CONTRIBUTING.mdcargo run --bin ty -- check /path/to/project/ty check會檢查項(xiàng)目中的類型錯(cuò)誤由于invalid-legacy-positional-parameter的默認(rèn)級別是warn在不額外配置時(shí)它就會出現(xiàn)在輸出中可用于直觀驗(yàn)證上文的觸發(fā)與修復(fù)示例。想要快速做一次驗(yàn)證可以建一個(gè)臨時(shí)文件寫入def f(x, __y): pass再對其執(zhí)行ty check。與其他規(guī)則的關(guān)聯(lián)在同一個(gè) lint 文檔目錄中還存在語義互補(bǔ)的兄弟規(guī)則 positional-only-parameter-as-kwarg它檢測的是調(diào)用側(cè)把 positional-only 參數(shù)按關(guān)鍵字傳入的誤用而invalid-legacy-positional-parameter關(guān)注的是定義側(cè)用遺留命名約定聲明 positional-only 卻聲明失敗的問題。二者分別從函數(shù)定義與函數(shù)調(diào)用兩個(gè)方向守護(hù) positional-only 語義的一致性與正確性。小結(jié)invalid-legacy-positional-parameter是一條面向歷史兼容代碼的類型檢查 lint它允許你在仍支持 Python ≤ 3.7 的前提下沿用 PEP 484 的__命名約定但會堅(jiān)決攔截試圖用遺留約定聲明、卻因位置靠后而實(shí)際無效的參數(shù)。修復(fù)時(shí)優(yōu)先推薦遷移到 Python 3.8 的/顯式語法若必須保持舊運(yùn)行時(shí)兼容則將__前綴參數(shù)整理到參數(shù)列表最前的連續(xù)區(qū)間即可。要深入掌握這條規(guī)則建議依次閱讀 規(guī)則文檔、規(guī)則聲明、核心判定邏輯 與 簽名構(gòu)建邏輯四者合起來即是約定是什么、為什么錯(cuò)、何時(shí)報(bào)告、底層如何判定的完整答案?!久赓M(fèi)下載鏈接】ruffAn extremely fast Python linter and code formatter, written in Rust.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ru/ruff創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考