Ruff 类型检查器中global引用的作用域语义:从 mdtest 测试规范到源码实现
【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff
导读
global是 Python 作用域规则中最微妙的关键字之一:它把函数内部的绑定与模块级命名空间联通,又与nonlocal、类作用域、嵌套函数产生大量边界行为。本文以 Ruff 仓库中 ty 类型检查器的 Markdown 测试规范 global.md 为骨架,系统梳理global引用的全部语义——从隐式全局查找、类型窄化、语义语法错误,到嵌套作用域可见性的"激进/保守"取舍——并结合 mdtest.py 运行器与 diagnostic.rs 中的诊断定义,说明这些行为如何被测试与实现。读完本文,你将完整掌握 ty 类型检查器对global的处理规则,并能直接复现这些测试用例验证行为。
测试规范的定位:mdtest 中的scopes/global.md
在 Ruff 仓库中,ty 类型检查器的语义行为以 Markdown 测试文档的形式沉淀在crates/ty_python_semantic/resources/mdtest/目录下,与 global.md 同级的还有 nonlocal.md、builtin.md、class_implicit_attrs.md 等,共同构成"作用域"(scopes)测试套件。
这些文档采用统一的执行约定:每个 Markdown 小节(##标题)是一个独立测试用例,文档中的 Python 代码块会被逐段送入类型检查器,代码内的reveal_type(x)调用会揭示x的推断类型,并写入# revealed: ...注释作为断言;# error: [诊断码] "消息"注释则断言某行必须产生指定诊断。运行器实现在 mdtest.py:它会先通过cargo test --package ty_python_semantic --test=mdtest编译测试二进制,再以过滤参数执行对应文档。例如只运行本文档,可以执行:
cargo run --package ty_python_semantic --bin mdtest -- scopes/global.mdmdtest.py还支持传入局部路径过滤(如'loops/for.md')以及--enable-external、--no-lockfile-upgrades、--no-snapshot-updates等参数,并默认监听resources/mdtest/目录下的.md变更实现热重跑。文档也可以携带 TOML 配置块(参见 mdtest_config.md),按标题层级继承配置——例如在根节配置[environment] python-version = "3.10"后,所有子节默认继承,子节又可覆盖。
基本语义:隐式全局与显式全局
隐式全局:未定义符号的函数内查找
global.md的第一条规则最基础:函数中对从未定义符号的名称引用,隐式地就是一次全局查找。换句话说,只要函数作用域内没有同名绑定,读取就会"穿透"到模块作用域:
x = 1 def f(): reveal_type(x) # revealed: Literal[1]这里x虽然在f内没有定义,但类型检查器能正确解析到模块层的x,并给出字面量类型Literal[1]。这正是 Python LEGB 作用域规则(Local → Enclosing → Global → Builtins)中 G 层查找的静态化体现。
显式全局:global语句声明
如果函数内部出现了global x声明,读取语义不变,只是把"隐式全局"变成"显式声明":
x = 1 def f(): global x reveal_type(x) # revealed: Literal[1]两者的类型结论一致。区别在于:global声明不仅影响读取,还授权函数对模块层绑定进行写操作,并且会触发一系列后续章节将讨论的语法约束与可见性规则。
全局绑定上的类型检查:赋值与声明错误
无效赋值(invalid-assignment)
一旦函数通过global声明了x,对x的赋值就必须与模块层x的声明类型兼容。在下面的例子中,模块层x: int,函数内部对x赋""就会触发invalid-assignment;而局部变量y的同类赋值错误则与全局无关,两者并列展示了局部与全局路径的检查是并行的:
x: int = 1 def f(): y: int = 1 # error: [invalid-assignment] "Object of type `Literal[""]` is not assignable to `int`" y = "" global x # error: [invalid-assignment] "Object of type `Literal[""]` is not assignable to `int`" x = ""无效声明(invalid-declaration)与合成定义
更微妙的场景是:函数内先global z再赋z = "",此时模块层尚未声明z。ty 的处理是——函数作用域内为z建立合成定义(synthetic definition),该定义在函数结束后回灌到模块作用域;由于z的实际推断类型是Literal[""],随后模块层再写z: int就会触发invalid-declaration:
x: int = 1 def f(): global x # error: [invalid-assignment] "Object of type `Literal[""]` is not assignable to `int`" x = "" global z # 允许:f 中 z 的无效声明充当 `z: Unknown`,与局部场景的 # x = 42 # ok # x: str # error # 结果类似。将来也可以在这里报错,关键是下面两行至少有一行应失败。 z = "" # 此声明看到了 f 结束后回灌到本作用域的 z 的合成定义 # error: [invalid-declaration] "Cannot declare type `int` for inferred type `Literal[""]`" z: int从源码结构看,这种"合成定义流经函数回到模块作用域"的机制与 ty 的绑定/定义推断模型(见crates/ty_python_semantic/src/types/infer/下的定义推断模块)一致:函数体的global写入被视为对定义作用域的一种潜在副作用,需要在定义作用域的类型推断中参与合并。
窄化(Narrowing):global声明不阻挡类型窄化
global声明不会破坏控制流分析中的类型窄化。赋值之后的读取能看到窄化结果:
x: int | None def f(): global x x = 1 reveal_type(x) # revealed: Literal[1]if分支同样适用:即便声明了global x,在if x == 1分支内x已知非None,可以放心赋值给int变量:
x: int | None def f(): # 这里的 global 关键字并非必需,但用于测试它不会妨碍窄化 global x if x == 1: y: int = x # 允许,因为此分支内 x 不可能是 None条件重绑定下的嵌套函数解析
当外层函数条件性地重绑定x时,内层函数依然要能穿过这层条件绑定解析到全局名。flag为真时x = 2并提前返回,为假时内层函数读取的x就可能是模块层的1或已被重绑定的2,因此揭示为联合类型:
x = 1 def outer(flag: bool) -> None: global x if flag: x = 2 return def inner() -> None: reveal_type(x) # revealed: Literal[1, 2]语义语法错误:global的合法性约束
nonlocal与global互斥
一个名字不能同时是nonlocal和global。这属于语义语法错误,会触发invalid-syntax诊断。原文特别指出:CPython 把错误标在nonlocal行,而 mypy、pyright 和 Ruff(PLE0115)标在global行,ty 与后者保持一致:
x = 1 def f(): x = 1 def g() -> None: nonlocal x global x # error: [invalid-syntax] "name `x` is nonlocal and global" x = None使用先于global声明
在同作用域内,先使用名字、后写global声明是语法错误(invalid-syntax),消息为"namexis used prior to global declaration"。原文档用大量排列组合覆盖了这一规则的边界:
- 单次
global声明在使用之后(读取场景):x = 1 y = 2 def f(): print(x) global x # error: [invalid-syntax] "name `x` is used prior to global declaration" print(x) - 重复声明时,只要任意一次
global位于使用之后即报错:def f(): global x print(x) global x # error: [invalid-syntax] "name `x` is used prior to global declaration" print(x) - 多名字声明
global x, y时,只要有名字被先使用即报错(对x报错,y不受影响):def f(): print(x) global x, y # error: [invalid-syntax] "name `x` is used prior to global declaration" print(x) - 先赋值再
global同样报错(赋值也构成"使用"前的绑定):def f(): x = 1 global x # error: [invalid-syntax] "name `x` is used prior to global declaration" x = 1 del操作同样构成"使用",规则一致:def f(): del x global x, y # error: [invalid-syntax] "name `x` is used prior to global declaration" del x- f-string 内插值读取也算使用:
def f(): print(f"{x=}") global x # error: [invalid-syntax] "name `x` is used prior to global declaration" - 该规则在模块作用域同样生效——即使
x已在模块层定义,先使用后global依然报错:# 模块作用域依然是错误 x = None global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
对global绑定做注解是语法错误
global x之后不能再写x: str = "foo"形式的带注解赋值:
x: int = 1 def f(): global x x: str = "foo" # error: [invalid-syntax] "annotated name `x` can't be global"这与nonlocal.md中 "annotated namexcan't be nonlocal" 的约束对应。不过,模块作用域中的global关键字本身是允许的(虽然无用),且不阻碍后续对同名变量的注解:
global y y: int = 42合法的重复声明:global之后的再次声明
只要global声明先于任何使用,后续的赋值、再声明都合法:
def f(): global x y = x x = 1 # 无错误 x = 2局部绑定遮蔽与公共类型可见性
局部绑定遮蔽全局公共类型
函数内global x之后,如果又产生了局部赋值,则该作用域内x的类型由局部绑定决定,遮蔽全局公共类型:
x = 42 def f(): global x reveal_type(x) # revealed: Literal[42, "56"] x = "56" reveal_type(x) # revealed: Literal["56"]注意第一次reveal_type的结果是Literal[42, "56"]:由于本作用域后续存在x = "56"这一局部绑定,且函数可能被多次调用,所以读到的类型是全局初始值 42 与局部绑定 "56" 的联合——这体现了下文的"后置绑定可见"规则。
局部赋值阻止回退到外层作用域
反过来,如果函数内没有global声明但有局部赋值,那么读取x不再回退到模块层,而是解析到本作用域尚未初始化的局部绑定,触发unresolved-reference:
x = 42 def f(): # error: [unresolved-reference] "Name `x` used when not defined" reveal_type(x) # revealed: Unknown x = "56" reveal_type(x) # revealed: Literal["56"]这正是"局部绑定 look ahead"机制:一旦作用域内存在同名绑定,先于该绑定的读取就视为"使用了未定义的名字",而不是回退到外层。
未使用的global声明也会影响推断
即使global声明后没有任何赋值,ty 也保守地假设它可能被使用,从而影响该名字的推断类型:
x = 1 def f(): global x # TODO: reveal_type(x) # revealed: Unknown | Literal["1"]文档中这仍是一个 TODO 断言(被注释掉),说明该行为尚未完全定案,但注释已经写出了预期的推断方向。
unresolved-global诊断:全局名需要显式定义
这是global语义中最重要的错误诊断之一。其定义位于 diagnostic.rs(UNRESOLVED_GLOBAL,并在同文件的register_lints中注册),消息模板与 lint 文档resources/lint_docs/unresolved-global.md关联。
未在全局作用域定义的新名字
可以用global声明一个全局作用域中完全没有定义的新名字,但 ty 认为这是可疑行为,倾向 lint 它:
x = 1 y: int # z 在全局作用域中既无绑定也无声明 def f(): global x, y, z # error: [unresolved-global] "Invalid global declaration of `z`: `z` has no declarations or bindings in the global scope"可以看到:x(有绑定)、y(有声明)都合法,只有z(既无绑定也无声明)报错。
隐式全局 vs 内置名
隐式全局(implicit global)不需要定义,但内置名(built-in)也不行:
def f(): global __file__ # 允许,隐式全局 global int # error: [unresolved-global] "Invalid global declaration of `int`: `int` has no declarations or bindings in the global scope"__file__这类由运行时提供的隐式全局可以声明;int这类 builtin 虽然随时可用,但并不是"全局作用域中的声明或绑定",因此global int被视为无效声明。
模块级未解析时的嵌套急切作用域
即使global声明在模块级无法解析(会报unresolved-global),同一函数内嵌套的急切作用域(如类体)仍然能看到已经发生的重绑定:
def factory(): global x # error: [unresolved-global] "Invalid global declaration of `x`: `x` has no declarations or bindings in the global scope" x = 1 class C: reveal_type(x) # revealed: Literal[1]类体在函数执行时被立即求值,x = 1已经生效,所以类体内能读到Literal[1],与global x本身无法解析到模块级定义互不冲突。
嵌套类与推导式:急切作用域对global重绑定的感知
显式模块级绑定在条件重绑定下依然可见
模块层有value = 0,外层函数条件性重绑定value,嵌套类应同时看到两种可能:
value = 0 def conditional_global_factory(flag: bool): global value if flag: value = "updated" class Nested: reveal_type(value) # revealed: Literal["updated", 0]若条件被静态判定为假(Literal[False]),嵌套类应只看到模块层原始绑定,且不报告未解析引用:
from typing import Literal known_false_value = 0 def known_false_global_factory(flag: Literal[False]): global known_false_value if flag: known_false_value = "updated" class Nested: reveal_type(known_false_value) # revealed: Literal[0]声明与内置名的条件绑定
模块层仅有声明(无绑定)时,条件绑定后的嵌套类看到声明类型:
declared_value: int def conditional_declared_global_factory(flag: bool): global declared_value if flag: declared_value = 1 class Nested: reveal_type(declared_value) # revealed: int条件重绑定下,未绑定快照会继续穿透到隐式全局(__file__的类型是str):
def conditional_factory(flag: bool): global __file__ if flag: __file__ = "shadow" class C: reveal_type(__file__) # revealed: str未绑定快照还可以穿过模块作用域直达 builtin——条件是global len本身非法(len是 builtin 而非全局声明),但仍揭示出len的类型:
def conditional_builtin_factory(flag: bool): global len # error: [unresolved-global] "Invalid global declaration of `len`: `len` has no declarations or bindings in the global scope" if flag: len = 1 class C: reveal_type(len) # revealed: Literal[1] | (def len(obj: Sized, /) -> int)推导式同样是急切作用域
列表推导式也是急切求值的嵌套作用域,能看到原始模块级绑定与条件性全局重绑定的并集:
value = 0 def factory(flag: bool): global value if flag: value = "updated" [reveal_type(value) for _ in [0]] # revealed: Literal["updated", 0]类作用域内的解析顺序与global回退
类作用域有一套特殊的名称解析规则:未定义之前访问的变量会回退到全局。原文档用一个综合用例系统地验证了各种情况:
import secrets x: str = "a" def f(x: int, y: int): class C: reveal_type(x) # revealed: int class D: x = None reveal_type(x) # revealed: None class E: reveal_type(x) # revealed: str x = None # error: [unresolved-reference] reveal_type(y) # revealed: Unknown y = None # 声明也算定义,即使没有绑定 class F: reveal_type(x) # revealed: str x: int reveal_type(x) # revealed: str # 显式 nonlocal 变量不算数,即使被绑定 class G: nonlocal x reveal_type(x) # revealed: int x = 42 reveal_type(x) # revealed: Literal[42] # 可能未绑定的变量与回退查找取并集 class H: if secrets.randbelow(2): x = None reveal_type(x) # revealed: None | str逐条解读:
C:类体内无x绑定,解析到函数参数x: int;D:类体内x = None先于读取,看到局部绑定None;E:读取在绑定之前,回退到外层x: str;而y既未绑定也未在外层定义,报unresolved-reference并得到Unknown;F:仅注解声明x: int(无绑定)也算定义,但不建立类型,所以读取仍是外层str;G:nonlocal x显式引用了函数参数,绑定x = 42后读取为Literal[42];H:条件性绑定导致可能未绑定,与回退查找取并集,得到None | str。
类体看不到正在定义的类名
类名在类体求值之后才绑定,因此类体内不能把类名解析到自身:
class A: A = A # error: [unresolved-reference] B = 1 class B: reveal_type(B) # revealed: Literal[1] B = B第一个class A中A = A右侧的A无法解析,报unresolved-reference;第二个class B则因为模块层已有B = 1,类体内的reveal_type(B)解析到模块层的Literal[1],B = B也合法。
嵌套与兄弟作用域中global绑定的可见性
这是global.md中篇幅最大、理论性最强的部分,也是与 nonlocal.md 中同名测试互相对应的一节。
基本现象:内层写入影响定义作用域的推断
内层作用域的global写入会影响全局(定义)作用域中该变量的推断类型;同理,嵌套函数中的读取也能看到自身作用域中更靠后的绑定。文档以三个命名文件逐步展开:
global1.py——先读后写:
x = 1 def f(): global x # 如果 f 在此之前被调用过,下面的赋值 2 就是可见的 reveal_type(x) # revealed: Literal[1, 2] x = 2 # 一旦本作用域建立绑定,就遮蔽外层作用域的绑定 reveal_type(x) # revealed: Literal[2] # 从现在起我们假设 f 可能在任意时刻被调用 reveal_type(x) # revealed: Literal[1, 2]这里揭示的类型依赖两个方向相反的假设:一是保守假设——f可能在最后的 reveal 之前被调用(尽管从源码看它从未被调用,"足够聪明的编译器"本可窄化到Literal[1],但 ty 不(能)追踪函数何时被调用,尤其在全局作用域);二是激进假设——f内赋值后立刻得到Literal[2],这在一般情况下并不健全。
global2.py——兄弟函数的反例:
x = 1 def f(): global x x = 2 reveal_type(x) # revealed: Literal[2] def g(): global x x = 3 f() # 上面给出 Literal[2] 的逻辑在这里同样给出 Literal[3],尽管 f() 的调用 # 意味着运行时 x 其实是 2。我们只能对这些做局部推理,无法做全程序控制流 # 分析,也无法解决停机问题。完全健全的类型检查器通常需要在这里和上面 # 都推断 Literal[2, 3]。那当然更好——错误的答案很糟!——但会破坏太多 # 期望在 f 这类简单场景中得到 Literal[2] 的真实世界代码。 reveal_type(x) # revealed: Literal[3] reveal_type(x) # revealed: Literal[1, 2, 3]文档明确承认:这里存在有意为之的不健全假设(unsound assumptions),目的是让简单、常见的场景符合用户直觉。
遮蔽与嵌套保留的双重策略
global3.py揭示了更精细的规则:激进地遮蔽外层/兄弟作用域的绑定,但保守地保留当前作用域之后遇到的内层绑定(按代码自上而下阅读的顺序):
x = 1 # 我们刚定义 x,尚未遇到任何它的嵌套绑定 reveal_type(x) # revealed: Literal[1] def bar(): global x # 尚未遇到 x 的任何局部绑定,其公共类型对 bar 可见,包括 bar 下面 # 自己对 3 的赋值 reveal_type(x) # revealed: Literal[1, 2, 3] x = 2 # 局部绑定遮蔽整个公共类型 reveal_type(x) # revealed: Literal[2] # 我们遇到了 3 的嵌套赋值,所以把它与局部绑定一起保留在本作用域中 reveal_type(x) # revealed: Literal[1, 2] x = 3 # 该赋值遮蔽了之前的局部绑定,但嵌套绑定依然可见 reveal_type(x) # revealed: Literal[2, 3]第二个行为同样不健全——嵌套函数可以"逃逸"其定义作用域并影响定义行之上读取的类型——但这是用户期望的行为。
嵌套global被外层局部隐藏
若内层的global x写入需要穿过outer作用域才能到达模块层,那么当x在outer内解析到局部绑定时,内层写入不应干扰这些作用域中的读取:
x = 1 def outer(): x = 2 def inner(): def writer(): global x x = 3 reveal_type(x) # revealed: Literal[2] reveal_type(x) # revealed: Literal[2] reveal_type(x) # revealed: Literal[1, 3]writer的global x写入(3)需要"流经"outer回到模块层,所以模块层读到Literal[1, 3];但outer及其内部作用域中的x解析到outer的局部绑定2,不受影响。
中间作用域中的可见性
嵌套的global绑定在中间作用域(非定义作用域、也未声明global/nonlocal,仅作为自由变量使用)也是可见的:
def _(): def _(): global x x = 1 global x x = 2 # 最内层函数的绑定在这里可见,因为它嵌套于本作用域之下; # 尽管本作用域不是 x 的定义作用域,也没有把 x 声明为 global #(而是把它当作"自由变量"使用) reveal_type(x) # revealed: Literal[1, 2] x = 3 reveal_type(x) # revealed: Literal[1, 2, 3]条件窄化可过滤嵌套绑定
isinstance窄化可以过滤掉不兼容的嵌套绑定:
x = 42 def hello(): global x x = "hello" reveal_type(x) # revealed: Literal[42, "hello"] if isinstance(x, int): reveal_type(x) # revealed: Literal[42]窄化后x必为int,嵌套绑定"hello"被排除。
条件性global绑定保留父作用域绑定
常规的分支合并规则同样作用于遮蔽行为:条件分支内的赋值会在分支内遮蔽公共类型,分支外恢复可见:
def flag(): ... x = 1 def foo(): global x x = 2 def bar(): global x if flag(): x = 3 # x 的公共类型在此被遮蔽... reveal_type(x) # revealed: Literal[3] # ...但在这里依然可见 reveal_type(x) # revealed: Literal[3, 1, 2]参数默认值先于函数体求值
正常执行时无需考虑这个顺序(函数体在被调用前不产生副作用),但在推断中必须考虑,因为存在"遇到嵌套绑定后视为可见"的(通常不健全的)规则:
x = 1 # 这里的 x 看不到下面的 x = 2 def f(y=reveal_type(x)): # revealed: Literal[1] global x x = 2global与局部/nonlocal的穿插
nonlocal声明不允许解析到对同一变量有global声明的作用域(这是语义语法错误),因此nonlocal绑定不能"穿过"一个同名global的作用域;反之,global绑定可以"穿过"中间那些将同名变量作为局部/nonlocal的作用域,只要所有nonlocal声明都合法解析。原文档用一个五层嵌套的综合用例验证了"free"、"global"、"nonlocal"三种读取视角:
x = 1 def _(): def _(): def global_middle(): def _(): def _(): def global_inner(): global x x = 2 # "free" 情形:我们看到父作用域中的局部 x reveal_type(x) # revealed: Literal[3] x = 3 global x # "global" 情形:我们看到全局 x reveal_type(x) # revealed: Literal[2, 1] nonlocal x # "nonlocal" 情形:我们看到父作用域中的*另一个*局部 x reveal_type(x) # revealed: Literal[4, 5] x = 4 x = 5 # "module" 情形:我们看到全局 x reveal_type(x) # revealed: Literal[1, 2]自由读取与窄化约束
合成嵌套绑定定义同时存储global和nonlocal嵌套写入,推断时再决定尊重哪些。对"自由读取"(当前作用域只有使用没有绑定)的作用域,本来几乎可以忽略该问题——推断最终会走到定义作用域去解析自由读取;但如果自由读取被窄化,嵌套绑定可能未被窄化,就变得重要了:
x: int | str = 1 def f(): def g1(): global x x = "g1" if isinstance(x, int): def g2(): global x x = "g2" # 窄化条件覆盖了 g1 的嵌套写入,但没有覆盖 g2 的。 # 要处理正确,必须检测到嵌套 global 写入在本作用域中可见。 reveal_type(x) # revealed: Literal["g2"] | intg1的写入在窄化条件之前,条件成立时已被排除;而g2定义在窄化分支内部,其写入不受窄化约束,因此揭示为Literal["g2"] | int。
增强赋值、复杂约束与已知限制
global的+=拓宽与循环一致
循环中使用+=会触发不动点分析:当Literal值列表达到上限后拓宽为int。global的增强赋值同理——内层函数体可能运行任意多次:
x = 1 def f(): global x x += 1 reveal_type(x) # revealed: int复杂性上限回退
在 nonlocal.md 中有明确记载(global场景同理):ty 对嵌套绑定定义设置了"过度复杂"上限,超过后停止考虑嵌套绑定,以保证性能。其表现是y的类型退化为Literal[0] | Unknown。
TODO:类作用域中的global写入应被急切应用
类体是急切求值的,所以类作用域中的global绑定应更像普通赋值。目前 ty 把类作用域当作函数作用域式的"惰性嵌套绑定"处理,导致类型比应有的更宽:
x = 1 class C: global x x = 2 # TODO: 应为 Literal[2] reveal_type(x) # revealed: Literal[1, 2] x = 3 # TODO: 应为 Literal[3] reveal_type(x) # revealed: Literal[2, 3]但类内函数作用域的嵌套绑定依然是惰性的;函数内的类体从函数调用者的视角看同样呈现惰性。这三条行为(类内直接写、类内函数写、函数内类体写)在文档中分别给出了对照用例,结论均为"当前揭示类型比理想类型宽"。
结语:从测试规范到类型检查器实现
回到本文的起点:global.md 不仅仅是一份文档,它是 ty 类型检查器global语义的可执行规格。每一条# revealed:与# error:注释都是对类型推断与诊断系统的硬性断言,运行器 mdtest.py 负责把 Markdown 编译成 Rust 测试并断言执行结果。当你修改类型检查器源码(如crates/ty_python_semantic/src/types/下的定义推断、绑定解析或诊断注册)后,只要执行cargo test --package ty_python_semantic --test=mdtest,这些语义行为就会被自动回归验证。
如果想进一步深入源码:UNRESOLVED_GLOBAL等诊断在 diagnostic.rs 中定义;unresolved-reference、invalid-assignment、invalid-declaration、invalid-syntax等诊断的触发点分散在crates/ty_python_semantic/src/types/infer/与crates/ty_python_semantic/src/place.rs等模块中;而与global行为严格对称的nonlocal语义,则可对照阅读 nonlocal.md。理解这份规范,就等于理解了 ty 在"局部/全局/非局部"三角关系上的全部设计取舍——包括那些为了用户体验而有意为之的不健全假设。
【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考