則解析:invalid-type-checking-constant 與 TYPE_CHECKING 常量約束)
Ruff Ty 類型檢查規(guī)則解析invalid-type-checking-constant 與 TYPE_CHECKING 常量約束【免費(fèi)下載鏈接】ruffAn extremely fast Python linter and code formatter, written in Rust.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ru/ruff導(dǎo)讀TYPE_CHECKING是 Python 類型系統(tǒng)中一個(gè)特殊變量在類型檢查器眼中它恒為True在運(yùn)行時(shí)卻必須為False承擔(dān)著把僅類型檢查器可見的代碼與運(yùn)行時(shí)執(zhí)行的代碼隔離的職責(zé)。本文以 ruff 倉(cāng)庫(kù)中 ty原生類型檢查器tycrate實(shí)現(xiàn)的invalid-type-checking-constant規(guī)則為核心講解該規(guī)則檢查什么、為什么必須這樣設(shè)計(jì)、ty 內(nèi)部是如何在賦值與注解兩個(gè)代碼路徑上實(shí)施檢查的并給出可落地的修正實(shí)踐。規(guī)則定位檢查什么規(guī)則invalid-type-checking-constant出自 ty_python_semantic 的 lint 文檔它檢查兩類問(wèn)題給TYPE_CHECKING變量賦了False以外的值給TYPE_CHECKING變量加的注解不是可以從bool賦值的類型。規(guī)則文檔中給出的兩個(gè)最小錯(cuò)誤示例為TYPE_CHECKING: str # error TYPE_CHECKING # error第一行給TYPE_CHECKING標(biāo)注了str類型——bool無(wú)法賦值給str注解非法第二行在無(wú)注解的裸賦值中給它賦了空字符串——不是字面量False同樣非法。從 ty 規(guī)則總覽文檔 可以看到該規(guī)則的注冊(cè)信息默認(rèn)級(jí)別為error自 ty 的0.0.1-alpha.1版本起加入。為什么必須這樣做TYPE_CHECKING 的雙面語(yǔ)義規(guī)則文檔解釋了其背后的核心原因TYPE_CHECKING這個(gè)名字被保留用作一個(gè)標(biāo)志位flag用來(lái)書寫只有類型檢查器看得到、運(yùn)行時(shí)不會(huì)執(zhí)行的條件代碼。正常情況下它從typing或typing_extensions導(dǎo)入但也可以由開發(fā)者在本模塊中自行定義。問(wèn)題的關(guān)鍵在于它的雙面語(yǔ)義運(yùn)行時(shí)本地定義時(shí)必須賦值為False這樣if TYPE_CHECKING:分支永遠(yuǎn)不會(huì)在運(yùn)行時(shí)執(zhí)行類型檢查期類型檢查器會(huì)一律把它的值視為True從而進(jìn)入if TYPE_CHECKING:分支分析其中的類型信息典型如import延遲導(dǎo)入、為類型檢查而設(shè)的引用。一旦違背這一約束——例如賦了True、空字符串、非bool可賦值的注解——類型檢查器對(duì)該名字的語(yǔ)義假設(shè)就會(huì)被破壞if TYPE_CHECKING:代碼塊的僅類型檢查可見這一性質(zhì)也就不再成立。這種語(yǔ)義在 ty 的測(cè)試文檔 mdtest/known_constants.md 中被系統(tǒng)性地驗(yàn)證。例如從typing導(dǎo)入的TYPE_CHECKING無(wú)論使用哪種引用方式其類型都被推導(dǎo)為reveal_type(TYPE_CHECKING) # revealed: Literal[True] reveal_type(typing.TYPE_CHECKING) # revealed: Literal[True]而在用戶自行定義TYPE_CHECKING False時(shí)即使字面量是False類型檢查器依然把它當(dāng)作True使用TYPE_CHECKING False reveal_type(TYPE_CHECKING) # revealed: Literal[True] if TYPE_CHECKING: ... # 類型檢查期可達(dá)分支也就是說(shuō)變量必須寫False、類型檢查器卻按True理解正是該規(guī)則的完整設(shè)計(jì)語(yǔ)義——校驗(yàn)只針對(duì)寫下的源碼而類型推導(dǎo)結(jié)果恒定指向Literal[True]。源碼實(shí)現(xiàn)兩處檢查路徑規(guī)則的核心診斷函數(shù)位于 crates/ty_python_semantic/src/types/diagnostic.rs#L3010-L3017pub(super) fn report_invalid_type_checking_constant(context: InferContext, node: AnyNodeRef) { let Some(builder) context.report_lint(INVALID_TYPE_CHECKING_CONSTANT, node) else { return; }; builder.into_diagnostic( The name TYPE_CHECKING is reserved for use as a flag; only False can be assigned to it, ); }診斷消息為The name TYPE_CHECKING is reserved for use as a flag; only False can be assigned to itTYPE_CHECKING這個(gè)名字被保留用作標(biāo)志位只有False可以被賦給它。從源碼結(jié)構(gòu)看該函數(shù)通過(guò)report_lint框架上報(bào)并在檢查點(diǎn)通過(guò)行內(nèi)信息或子診斷給出補(bǔ)充說(shuō)明。真正判定是否違規(guī)的邏輯在類型推導(dǎo)器 crates/ty_python_semantic/src/types/infer/builder.rs 中共分兩條路徑。路徑一無(wú)注解的賦值語(yǔ)句在builder.rs的賦值推導(dǎo)分支中約 L3593-L3607源碼注釋明確指出TYPE_CHECKINGis a special variable that should only be assignedFalseat runtime, but is always consideredTruein type checking.TYPE_CHECKING是特殊變量運(yùn)行時(shí)只應(yīng)賦False而類型檢查期總視為True。參見 mdtest/known_constants.md 中 User-defined TYPE_CHECKING 一節(jié)。對(duì)應(yīng)的檢查邏輯為當(dāng)賦值目標(biāo)是名字恰好為TYPE_CHECKING的名字表達(dá)式且右側(cè)值不是布爾字面量False即ExprBooleanLiteral { value: false }時(shí)調(diào)用report_invalid_type_checking_constant報(bào)告錯(cuò)誤隨后無(wú)論字面量是什么都把該名字的類型綁定為Type::bool_literal(true)即Literal[True]。這解釋了開篇示例第二行TYPE_CHECKING 為何報(bào)錯(cuò)。路徑二帶類型注解的聲明另一處檢查發(fā)生在處理帶注解變量聲明/注解賦值時(shí)約 L4676-L4703邏輯分為三步先校驗(yàn)注解若KnownClass::Bool的實(shí)例類型無(wú)法賦值給聲明的注解類型declared.inner_type()即注解不接受bool直接報(bào)告invalid-type-checking-constant——對(duì)應(yīng)文檔示例第一行TYPE_CHECKING: str否則注解可接受bool再校驗(yàn)文件類型與初值若處于 stub 文件.pyi代碼內(nèi)self.in_stub()為真且初值缺失或?yàn)?..則視為合法stub 中只寫TYPE_CHECKING: bool或TYPE_CHECKING: bool ...是被允許的聲明方式其余情況下初值只要不是布爾字面量False就報(bào)告錯(cuò)誤最后無(wú)論注解如何都把聲明的內(nèi)部類型改寫為Type::bool_literal(true)。綜合兩條路徑一個(gè)合法的本地定義需要同時(shí)滿足兩個(gè)條件注解類型接受bool推薦直接寫bool初值必須是字面量False。ty 的測(cè)試文檔將這一規(guī)則以行為示例固化了下來(lái)見 mdtest/known_constants.md 中 Invalid assignment to TYPE_CHECKING 一節(jié)包括TYPE_CHECKING True # error賦值不是 False TYPE_CHECKING: bool True # error賦值不是 False TYPE_CHECKING: int 1 # errorbool 不能賦值給 int TYPE_CHECKING: str str # error注解與初值均非法 TYPE_CHECKING: str False # error注解不接受 bool TYPE_CHECKING: Literal[False] False # error注解類型不接受 bool TYPE_CHECKING: Literal[True] False # error同上注意最后兩類盡管初值是False但Literal[False]/Literal[True]這類窄化注解仍被判定為非法——因?yàn)轭愋蜋z查器會(huì)把該變量最終視為L(zhǎng)iteral[True]窄到單一字面量的注解與這一推導(dǎo)結(jié)果不自洽。唯一的合法注解形式是TYPE_CHECKING: bool False。實(shí)踐指引合法與非法寫法對(duì)照完全合法的定義方式# 方式一無(wú)注解、直接賦 False最常見 TYPE_CHECKING False # 方式二顯式注解為 bool初值 False TYPE_CHECKING: bool False # 方式三stub 文件中.pyi可省略初值或用省略號(hào)占位 # TYPE_CHECKING: bool # TYPE_CHECKING: bool ...當(dāng)TYPE_CHECKING為False時(shí)類型檢查器依然將其視為True因此下列慣用代碼模式延遲導(dǎo)入可以安全通過(guò)TYPE_CHECKING False if TYPE_CHECKING: from some_heavy_module import HeavyClass # 僅類型檢查可見不產(chǎn)生運(yùn)行時(shí)導(dǎo)入 def f(x: HeavyClass) - None: # 注解可解析 ...必須修正的寫法TYPE_CHECKING: str # errorbool 無(wú)法賦值給 str TYPE_CHECKING # error初值非 False TYPE_CHECKING True # error初值非 False修正建議若只是為了運(yùn)行期恒假、類型期恒真的分支控制優(yōu)先選擇從typing或typing_extensionsimport TYPE_CHECKING完全規(guī)避自定義帶來(lái)的約束問(wèn)題必須自定義時(shí)刪去不必要注解并寫TYPE_CHECKING False若需要顯式注解使用bool并同時(shí)確保初值為字面量False在.pyistub 文件中可寫為TYPE_CHECKING: bool或TYPE_CHECKING: bool ...這是源碼中明確放行的兩種 stub 聲明形態(tài)。相關(guān)文件索引若希望深入閱讀本規(guī)則的文檔、實(shí)現(xiàn)與行為測(cè)試可依次查看以下倉(cāng)庫(kù)文件規(guī)則 lint 文檔crates/ty_python_semantic/resources/lint_docs/invalid-type-checking-constant.md診斷上報(bào)函數(shù)crates/ty_python_semantic/src/types/diagnostic.rs#L3010-L3017類型推導(dǎo)與判定邏輯crates/ty_python_semantic/src/types/infer/builder.rs#L3593-L3607 與 crates/ty_python_semantic/src/types/infer/builder.rs#L4676-L4703行為測(cè)試mdtest 用例crates/ty_python_semantic/resources/mdtest/known_constants.mdty 規(guī)則注冊(cè)與默認(rèn)級(jí)別crates/ty/docs/rules.md#L3275-L3294總結(jié)invalid-type-checking-constant規(guī)則守護(hù)的是 Python 類型系統(tǒng)中最容易被誤解的常量語(yǔ)義TYPE_CHECKING的源碼寫False、檢查期讀True的雙面契約。ty 在實(shí)現(xiàn)上分別覆蓋了裸賦值與注解聲明兩條路徑并對(duì) stub 文件中的省略初值與...占位做了特例放行理解這套規(guī)則后你在書寫if TYPE_CHECKING:隔離塊與相關(guān)延遲導(dǎo)入時(shí)就能既寫出類型檢查器認(rèn)可的代碼又不引入任何運(yùn)行時(shí)開銷?!久赓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),僅供參考