ARTICLE DETAIL

资讯详情

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

ty 规则参考手册深度解读:133 条 Python 类型检查规则的原理、默认级别与配置实战

ty 规则参考手册深度解读:133 条 Python 类型检查规则的原理、默认级别与配置实战 ty 规则参考手册深度解读133 条 Python 类型检查规则的原理、默认级别与配置实战【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff本文以仓库内 crates/ty/docs/rules.mdty 类型检查器自动生成的规则参考文档为主体骨架展开。文中将以源码实现、示例代码与配置方式为佐证系统梳理 ty 内置的 133 条类型检查规则它们各在什么场景下触发、默认的启用级别是什么、如何在pyproject.toml或ty.toml中按规则或按目录调整、以及在代码中用ty: ignore精确抑制单条诊断。读完本文你将能够把规则名与检查原理一一对应并根据项目实际把默认忽略的强校验规则如disjoint-cast、division-by-zero按需打开。这份规则文档是如何生成的规则文档 文件头部有一行机器生成警告明确说明该文件不是手工维护的而是由cargo dev generate-all从类型检查器内的 lint 声明rule declaration自动渲染而来想修改其中任何内容都需要回到源码中的声明处修改 doc 注释。从文档内的 View source 链接可以确认绝大多数规则的声明与消息文本都集中在crates/ty_python_semantic/src/types/diagnostic.rs存放规则诊断每个规则带#[lint(...)]元数据对应规则名、默认级别、源代码行号crates/ty_python_semantic/src/types/string_annotation.rs字符串化前向注解相关的规则crates/ty_python_semantic/src/suppression.rsty: ignore/type: ignore抑制机制相关规则。每条规则在文档中的条目结构是高度一致的形成一套可机读的固定模板字段含义Default level该规则默认严重级别error/warn/ignoreAdded in该规则从哪个 ty 版本开始引入如0.0.1-alpha.1、0.0.64What it does规则检查什么Why is this bad违反它的运行时或类型安全后果Example(s)会触发诊断的代码以及有时配套的Use instead修正写法可选附加节Rule status默认禁用原因、Default level单独说明默认关闭的规则、Known problems、See also、References、Ruff rule按当前文档统计133 条规则中有94 条默认error、24 条默认warn、15 条默认ignore。也就是说绝大多数检查开箱即用即报错少数误报率偏高如division-by-zero或强调更严格健全性如disjoint-cast的规则默认关闭需用户主动开启。默认级别体系与三级规则开关规则级别的含义在 配置文档 中被精确定义ignore禁用该规则warn启用该规则产生 warning 诊断error启用该规则产生 error 诊断。配置项类型为dict[RuleName | all, ignore | warn | error]其中键可以是单个规则名也可以用all为所有规则设置一个兜底级别。典型配置如下两种配置文件等价前者为 pyproject.toml 中的[tool.ty]命名空间写法后者为独立ty.toml顶层写法# pyproject.toml [tool.ty.rules] possibly-unresolved-reference warn division-by-zero ignore# ty.toml [rules] possibly-unresolved-reference warn division-by-zero ignore关于退出码有一个容易忽略的语义只要产生任何 warning 或 error 诊断ty 默认就以退出码 1 结束。如果希望仅有 warning 时也通过 CI可以关闭配置项terminal.error-on-warning见 crates/ty/docs/configuration.md[tool.ty.terminal] error-on-warning false上述开关与本文后面逐条解读的规则名一一对应例如把默认ignore的division-by-zero改成error即可让除零这类静态可判定的运行时错误升级为硬性失败。按行抑制ty: ignore规则家族除了全局级别开关ty 还支持在源码中按行抑制。规则文档中的blanket-ignore-comment、ignore-comment-unknown-rule、unused-ignore-comment、unused-type-ignore-comment、invalid-ignore-comment这组规则正是围绕抑制注释本身做的卫生检查其实现位于 suppression.rs。blanket-ignore-comment默认ignore检查没有指明规则号的ty: ignore注释。一条笼统的ty: ignore会吞掉整行/整个文件上的所有诊断可能把无关错误一并静音带规则号的写法能自文档化这里预期有哪类诊断# error value unknown # ty: ignore # 推荐 value unknown # ty: ignore[unresolved-reference]ignore-comment-unknown-rule默认warn检查ty: ignore[code]或type: ignore[ty:code]中code不是已知规则名的情况——拼错规则号不会抑制任何错误且基本可以断定是笔误a 20 / 1 # ty: ignore[division-by-zer] # 拼写错误不生效 a 20 / 0 # ty: ignore[division-by-zero] # 正确另外配置项analysis.respect-type-ignore-comments默认开控制 ty 是否尊重标准的type: ignore注释若关掉则只有ty: ignore生效详见 配置文档。静态可判定的运行时错误类规则这一类规则最有直观价值ty 能在编译期发现 Python 必然抛出的TypeError/ValueError/IndexError/ZeroDivisionError且不需要运行代码。调用非可调用对象call-non-callable默认error# TypeError: int object is not callable 4() # error越界索引index-out-of-bounds默认error对字面量容器做静态下标判定t (0, 1, 2) # IndexError: tuple index out of range t[3] # error除零division-by-zero默认ignore——文档明确说明该规则因可能产生大量误报而默认关闭但字面量除零本身是无争议的5 / 0 # error零步长切片zero-stepsize-in-slice默认error。文档也如实列出了 Known problems该检查并非穷尽式只报告 Python 内建序列类型上已知会失败的零步长切片因为自定义__getitem__无法静态预判values list(range(10)) # ValueError: slice step cannot be zero values[1:10:0] # error类定义的静态合法性是一个密集的规则族全部默认error因为它们绝大多数都会在 import 阶段抛TypeErrorduplicate-base重复基类class B(A, A)inconsistent-mro无法形成一致 MRO如class C(A, B)且B已是A的子类conflicting-metaclass新建类的元类不是所有基类元类的子类如class C(A, B)中A、B分别用不同type子类作元类invalid-base基类不是type的实例如class A(42)instance-layout-conflict实例内存布局冲突包括多个定义非空__slots__的基类多重继承、以及多个内建类多重继承如class A(int, float)。文档的 Known problems 也坦承无法静态确定__slots__值、以及大量 C 扩展类使用扩展内存布局的情形无法穷举cyclic-class-definition仅针对 stub 文件.pyi因为 stub 虽原生支持前向引用但自引用继承环无法得到一致 MRO# foo.pyi class A(B): ... # error class B(A): ... # error上下文管理器与 await 协议invalid-context-manager默认error检查with语句右值未实现上下文管理器协议invalid-await默认error检查被await的值不是 Awaitable二者运行时都会抛TypeErrorwith 1: # error print(2) async def main() - None: await 42 # error异常相关invalid-exception-caught默认error检查except捕获了非异常类对应 Ruff 的B030规则正确写法是捕获BaseException子类invalid-raise族同样默认开启。super()参数校验invalid-super-argument默认error文档给出清晰的判定矩阵——第一参必须是类字面量第二参需满足isinstance(obj, type)或issubclass(obj, type)super(A, B()) # OK super(A(), B()) # error第一参不是类 super(B, A()) # errortotal_ordering有效性invalid-total-ordering默认error类至少要定义一个排序方法__lt__/__le__/__gt__/__ge__否则运行时抛ValueError。赋值、声明与返回类型类型一致性强校验这一类直接对应类型系统是否被破坏全部默认error也是类型检查器最核心的职责。invalid-assignment值类型不可赋值给标注类型如a: int invalid-declaration既有符号的推断类型与事后声明类型不可赋值如a 1; a: strconflicting-declarations同一变量被声明为两种冲突类型if __name__ __main__: a: int else: a: str a 1 # errorinvalid-argument-type实参类型不匹配形参func(foo)传给def func(x: int)invalid-return-type返回值无法赋给函数标注的返回类型def func() - int: return a # error: [invalid-return-type]empty-body默认error函数体只有.../pass/docstring、却标注了非None返回类型。文档专门说明该规则与invalid-return-type动机相同、但拆成独立错误码方便用户在原型开发阶段单独关闭它。同时列出合法的空体即声明例外.pyistub、Protocol 方法、abstractmethod、overload、TYPE_CHECKING块。属性访问的类/实例边界invalid-attribute-access默认error检查三个方向的越界从实例给类变量ClassVar赋值从类访问/写入仅在实例方法里经self赋值声明的实例专用变量通过泛型类或其特化别名读写依赖类型参数的泛型实例属性泛型特化不产生独立的类属性存储。class C: instance_var: int class_var: ClassVar[int] 1 def __init__(self): self.instance_var 42 self.instance_only_var: int 42 C().class_var 3 # error不能从实例写类变量 C.instance_only_var 56 # error不能从类写实例专用变量 class Box(Generic[T]): value: T Box[int].value 1 # error Box.value # error box Box[int]() box.value 1 # OK继承与方法重写LSP 一族规则invalid-method-override默认error检查违反里氏替换原则LSP的方法重写子类重写方法必须接受父类方法接受的所有实参组合且返回类型必须是父类返回类型的子类型。文档中的反例非常直观class Super: def method(self, x) - int: return 42 class Sub(Super): # Liskov violationstr 不是 int 的子类型 def method(self, x) - str: # error return foo class Sub2(Super): # Liskov violation父类可用关键字 x 调用子类却不接受 def method(self, y) - int: # error return 42文档还针对两个高频疑问给出 FAQ其一ty 强制__eq__/__ne__第二参必须接受object其二Ruff 的ARG002等规则建议把未用形参改名加_可能因此触发invalid-method-override——解决方式是给方法加typing.override让 Ruff 意识到这是有意的重写而不再建议改名。配套规则还包括invalid-explicit-override默认error加了override却并未真正重写任何父类方法object.__repr__这类隐式父类方法除外invalid-attribute-override默认error重写时把类变量与实例变量类别互换违反 LSPoverride-of-final-method、override-of-final-variable尝试重写被final标记的方法/变量subclass-of-final-class继承final类。abstractmethod与final的组合语义这是 ty 中自成体系、语义互相印证的一组规则绝大多数默认errorabstract-and-final-method同一方法既abstractmethod又final。抽象方法必须被子类覆盖才能具体化final 又禁止覆盖两者组合使子类永远无法给出实现abstract-method-in-final-classfinal类中留有未实现的抽象方法。文档特别指出运行时只有ABCMeta元类的类实例化会被阻止而类型检查器对任何类都执行此检查因为abstractmethod已明确表达本类应抽象的意图call-abstract-method在类对象上直接调用只有平凡函数体仅.../pass/docstring/raise NotImplementedError的抽象classmethod/staticmethod。文档解释了深层原因ty 允许这类平凡体抽象方法标注非None返回类型预期它总会被覆盖直接调用会导致推断出错误类型。经type[X]调用则是允许的因为实际运行时类型可能是已实现的具体子类class Foo(ABC): classmethod abstractmethod def method(cls) - int: ... Foo.method() # errorfinal-on-non-methodfinal只能用于方法与类用在模块级或嵌套函数上无效明显是笔误final-without-valueFinal符号声明后从未获得值。模块/函数作用域必须同作用域赋值类体中可在__init__赋值Protocol 成员作为接口声明则豁免ineffective-final默认warnfinal()以函数形式直接调用如final(type(MyClass, (), {}))类型检查器无法识别从而不阻止继承应改用装饰器形式。dataclass、Enum、NamedTuple 与 Protocol 特化校验dataclass 规则族全部默认errordataclass-field-order必填字段出现在有默认值字段之后——这是 Python 运行时就会抛TypeError的要求duplicate-kw-onlydataclass 中出现两个KW_ONLY标记该标记只能使用一次invalid-dataclass不兼容的dataclass参数组合orderTrue配eqFalse、weakref_slotTrue配slotsFalse、slotsTrue却已有__slots__以及给NamedTuple/TypedDict/Enum/Protocol子类加dataclass其中 Enum 的组合是官方明确不支持的Protocol 表示接口不可实例化invalid-dataclass-overridefrozenTrue的 dataclass 自定义了__setattr__/__delattr__——冻结 dataclass 会合成抛FrozenInstanceError的这两个方法再覆盖会引发运行时错误相关还有invalid-frozen-dataclass-subclass、subclass-of-dataclass-with-order、missing-slot等分别处理冻结子类化、带order的子类化与__slots__遗漏问题。Enuminvalid-enum-member-annotation默认warn——typing 规范要求检查器对 enum 成员一律推断字面量类型显式注解是误导性的因为运行时实际类型是枚举类本身class Pet(Enum): CAT 1 # OK DOG: int 2 # errorNamedTupleinvalid-named-tuple族默认error覆盖三类非法定义——除Generic外的多重继承运行时TypeError、以下划线开头的字段名运行时ValueError、以及覆盖_asdict/_make/_replace等合成属性运行时AttributeError。super-call-in-named-tuple-method、invalid-named-tuple-override为配套检查。Protocolambiguous-protocol-member默认warn协议类中出现未声明的隐式成员赋值会让接口含混、导致类型检查器推断出意外结果。文档给出正反例类体中显式注解的a: int、def方法定义都算声明而self.implicit value、c some variable、乃至循环变量for d, e in ...都会在协议体内产生未声明的隐式赋值被判为歧义成员isinstance-against-protocol、invalid-protocol分别处理对协议类做isinstance与协议定义本身非法的问题。泛型、类型变量与类型别名cyclic-type-alias-definition默认error精确刻画了合法递归别名的边界递归引用只要发生在另一类型内部就是合法的如type Tree int | list[Tree]别名直接展开成自身、或以自身为联合成员、或在两个别名间互为引用则都是非法的。该检查同时覆盖type语句与TypeAliasType构造的别名type Itself Itself # error type A B # error type B A # error type IntOr int | IntOr # error Cycle TypeAliasType(Cycle, Cycle) # error type Tree int | list[Tree] # 合法递归别名同一族还包括invalid-type-alias-typeTypeAliasType用法校验、invalid-generic-class/invalid-generic-enum泛型类/枚举定义合法性、missing-type-argument使用泛型但缺类型实参、invalid-type-arguments类型实参个数/种类错误、shadowed-type-variable与unbound-type-variable类型变量遮蔽/未绑定、invalid-type-variable-bound/invalid-type-variable-constraints/invalid-type-variable-default上界/约束/默认值非法、invalid-legacy-type-variable旧式类型变量写法、invalid-paramspecParamSpec 使用错误、invalid-typed-dict-field/header/statementTypedDict 定义三要素与missing-typed-dict-key构造时缺少必填键等几乎全部默认error。cast、assert_type与类型守卫健全性的逃生舱审计这一族是 ty 在允许逃生舱的前提下做部分校验的设计哲学的集中体现。disjoint-cast默认ignore0.0.78 引入是最有代表性的一条。文档先给出不相交disjoint的精确定义——两类型完全没有重叠例如str与int由于 CPython 禁止二者间多重继承int, str会抛 multiple bases have instance lay-out conflict唯一的公共子类型是无人居住的Never。然后点明设计动机cast()是有意设计为既不被运行时验证、默认也不被类型检查器验证的逃生舱允许大量有用的不安全窄化因此不可能全面校验但对完全不相交类型的 cast 几乎必然是 bug 或误解值得单独盯防def parse(value: int) - str: return cast(str, value) # error: [disjoint-cast]文档还给出了两个不相交性可能出乎意料的案例由于list是可变的、不变invariant的容器list[int]与list[bool]不相交即便bool : int两个NewType即使底层同为int除非显式声明父子关系也互不相交。相应替代方案包括改用协变容器SequenceSequence[int]→Sequence[bool]无诊断、构造新列表、或使用带运行时校验的TypeIs/TypeGuard。See also 部分还提示Ruff 的banned-api规则可以彻底封禁cast()而 ty 自己的redundant-cast负责检测值已经是目标类型的多余 cast。assert-type-unspellable-subtype默认error解释了一个精细场景assert_type()的本意是断言推断类型与声明类型完全相同但 ty 有超出用户可写注解的非标准类型扩展例如if x:窄化后 x 的实际类型是int ~AlwaysFalsy比int更精确且拼写不出来。此时 ty 改用独立的错误码assert-type-unspellable-subtype而非type-assertion-failure让用户能区分两种情况def _(x: int): assert_type(x, int) # fine if x: # 实际类型是 int ~AlwaysFalsy assert_type(x, int) # error: [assert-type-unspellable-subtype]配套的还有type-assertion-failureassert_type推断类型与声明不一致、static-assert-error、invalid-type-guard-definition、redundant-condition/redundant-condition-strict恒真/恒假条件与窄化、以及unsound-assignment/unsound-return-statement/unsound-yield针对动态类型的不健全但可能成立的赋值/返回/产出等。调用点实参完整性ty 对调用做了多角度的参数审计多数默认errormissing-argument缺少必填实参too-many-positional-arguments位置实参过多覆盖*args之外的越界情形unknown-argument实参名没有对应的形参positional-only-parameter-as-kwarg把仅限位置positional-only的参数当关键字传parameter-already-assigned同一参数被重复赋值invalid-parameter-default形参默认值类型不合法no-matching-overload与invalid-overload/useless-overload-body重载调度无匹配、重载定义非法或重载体无意义call-top-callable默认error调用被窄化到Top[Callable[..., T]]的对象——即经callable(x)或isinstance(x, Callable)后我们知道它可调用、却不知道精确签名它可能是无参函数也可能是必须带参函数的并集因此没有任何一组实参能保证有效def f(x: object): if callable(x): # 只知道 x 可调用不知道它接受什么参数 x() # errordynamic-function-decorator-return默认ignore装饰器把函数替换成Any/Unknown之类动态类型导致原签名信息与检查能力一并丢失。文档指出它相比 RuffANN201/ANN202的独特价值能抓住第三方库缺失注解导致非健全类型泄漏进你的一手代码这一 linter 无能为力的场景见前文untyped_decorator示例并给出用FunctionT泛型包装做类型安全兜底的修复方案。未解析引用与导入unresolved-*/possibly-*与动态模块围绕找不到名字/模块的情形ty 配置了成对的错误码unresolved-import、unresolved-reference、unresolved-global、unresolved-attribute用于完全无法解析的硬错误possibly-unresolved-reference、possibly-missing-import、possibly-missing-submodule、possibly-missing-attribute、possibly-missing-implicit-call则用于目标确实存在、但相关部件可能缺失的软场景例如从可解析模块里导入一个可能不存在的子模块或属性。missing-direct-dependency用于检查未直接声明的依赖间接依赖 import。invalid-module-getattr-call默认error则覆盖一种隐蔽情况模块定义了__getattr__兜底from module import missing会调用它并传参若其签名不匹配则 import 抛TypeError# module.py def __getattr__() - str: # 不接受任何参数 return fallback # main.py from module import missing # error__getattr__() 收到 1 个位置实参对无法解析的模块配置文档 还提供两级缓解analysis.allowed-unresolved-imports仅抑制诊断支持*/**/!glob 语法如[test.**, !test.foo]与analysis.replace-imports-with-any把pandas.**、numpy.**等匹配模块的 import 直接替换为Any即便模块可解析也会无条件替换其类型信息。字符串化前向注解的可解析性Python 允许注解是字符串字面量以实现前向引用但这些字符串必须能被解析为普通 Python 表达式。ty 为此设置了三个默认error的规则实现集中在 string_annotation.rsinvalid-syntax-in-forward-annotation字符串内容根本无法作为 Python 表达式解析如instance of C应写成Cescape-character-in-forward-annotation注解含转义字符如intt\b静态分析工具无法分析implicit-concatenated-string-type-annotation注解位置使用了隐式字符串拼接Literal[ 5 ]应合并为单个字符串Literal[5]。弃用与实验性语法deprecated默认warn检测对已弃用条目的使用。文档示例使用warnings.deprecated装饰器标注需 Python 3.13experimental-syntax默认warn检测不属于 Python typing 规范的实验性语法ty 特有扩展如交错类型A B、取反~A示例环境为 Python 3.14。文档明确警告其他类型检查器可能拒绝、未来可能不被标准化或发生破坏性变更。完整规则速查清单除上面详解的规则外ty 还包含以下规则默认级别见文档原文。为避免篇幅失控、同时保证读者可以对全部 133 条规则建立索引这里按主题族把剩余条目归纳列出每条规则的完整 What it does / Why is this bad / Example 均可回查 rules.md 对应锚点## 规则名控制流与冗余possibly-missing-attribute、possibly-missing-implicit-call、redundant-condition、redundant-condition-strict、unsupported-bool-conversion、unsupported-operator、unsupported-base、unsupported-dynamic-base、invalid-yield、unsound-yield、unused-awaitable抑制注释卫生unused-ignore-comment、unused-type-ignore-comment、invalid-ignore-comment类型别名/变量边界invalid-legacy-type-variable、invalid-type-form、invalid-type-arguments、mismatched-type-name声明的类名与名字不符特殊类语义invalid-typed-dict-*族、isinstance-against-typed-dict、non-callable-init-subclass、unavailable-implicit-super-arguments__init_subclass__隐式调用实参不可用、pydantic-discarded-extra-argumentPydantic 风格多余的额外实参被丢弃的检测默认warn级附近、super-call-in-named-tuple-method元编程与装饰器undefined-revealreveal_type/reveal_locals上下文非法时提示等。建议在阅读上述任一规则条目时先看标题行的Default level再读 Example 中的# error注释最后对照 Why 部分理解设计取舍——尤其是标ignore的规则disjoint-cast、division-by-zero、blanket-ignore-comment、dynamic-function-decorator-return等它们默认关闭往往出于误报率或受众高级用户/严格模式考虑而非检查无价值。如何将规则落地到项目综合 cli.md、configuration.md 与本文一个可复制的落地流程是先以默认级别运行一遍 ty处理所有默认error对照规则文档把确实想放行的规则在[tool.ty.rules]中置ignore而不是用笼统ty: ignore掩耳盗铃对希望更严格的代码库按需把disjoint-cast、division-by-zero等从ignore提到warn/error若使用type: ignore生态如 mypy 迁移确认analysis.respect-type-ignore-comments若全面改用ty: ignore[规则名]可依赖blanket-ignore-comment与ignore-comment-unknown-rule保持抑制注释的自文档化与可维护性。需要提醒的是规则的默认级别会随版本演进与Added in版本号在文档中持续更新——例如disjoint-cast0.0.78 引入与dynamic-function-decorator-return0.0.73 引入都是相对较新的严格型规则遇到具体版本差异时以当前仓库 rules.md 及对应规则在 crates/ty_python_semantic/src/types/diagnostic.rs 中的#[lint(...)]声明为准。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表