news 2026/9/29 5:23:45

Pyre Query 命令完全指南:不跑全量检查也能获取类型信息

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Pyre Query 命令完全指南:不跑全量检查也能获取类型信息
  • 静态分析
  • 开发工具
  • 代码质量

【免费下载链接】pyre-check

Performant type-checking for python.

项目地址:https://gitcode.com/gh_mirrors/py/pyre-check
点击查看免费下载

Pyre(Performant type-checking for python)是 Meta 开源的 Python 类型检查器。query子命令允许你直接挂钩到一个正在运行的 Pyre 服务器,在不执行完整类型检查的前提下获取类型相关信息——例如查询某个表达式在指定行列的类型、判断一个类型是否为另一个类型的子类型、列出某个类的全部方法,甚至导出整个类继承体系。读完本文,你将掌握pyre query的全部内置查询命令、典型 JSON 输出格式、位置(location)计算规则、批量查询与缓存机制,以及底层从客户端到 OCaml 服务端的完整调用链。

重要前置说明:这些接口属于 Legacy 代码

在深入之前必须先了解官方给出的重要警告:这些查询接口被 Pyre 团队视为 legacy 代码,远未达到生产级成熟度(短期内 Pysa 场景只会得到极少的维护),长期来看会被移除。官方明确建议:仅将它们用于调试和人工排查(manual triaging)场景,强烈不建议在其上构建任何自动化流程或产品。

因此,本文内容适合用来理解 Pyre 服务器内部的工作原理、排查类型问题或进行手工分析,但不适合作为长期稳定的工程依赖。

快速开始:启动服务器并执行第一个查询

要使用查询功能,首先需要一个正在运行的 Pyre 服务器。有两种方式:

# 方式一:直接运行 pyre(会自动启动服务器并执行检查) $ pyre # 方式二:显式启动常驻服务器 $ pyre start

随后即可用pyre query "<query>"发起查询。查询字符串是一种"伪 Python"表达式,会被 Pyre 服务端解析成对应的Request.t并处理(见后文源码解析章节)。

# 查看所有可用查询的完整列表 $ pyre query help

该帮助文本由 client/commands/query.py 中的HELP_MESSAGE常量提供,覆盖了本文介绍的全部命令(此外还包括dump_call_graph、inline_decorators、expression_level_coverage等命令)。

提示:美化输出。本文示例中的响应都经过python -m json.tool格式化:

$ pyre query "<query>" | python -m json.tool

如果服务器未运行,客户端会返回SERVER_NOT_FOUND退出码并输出提示:"A running Pyre server is required for queries to be responded. Please runpyrefirst to set up a server."(见 client/commands/query.py)。

支持的查询(Supported Queries)

以下按字母序逐一介绍所有查询命令,每个命令都配有最小可运行示例与真实返回结构。示例中的文件都假设位于 Pyre 配置所覆盖的项目根目录内。

Attributes:列出类的全部属性

attributes(class_name)返回某个类的全部属性列表,包括方法。给定如下代码:

# a.py class C: a: int = 2 def foo(self) -> str: return ""

执行:

$ pyre query "attributes(a.C)"

返回:

{ "response": { "attributes": [ { "annotation": "int", "name": "a" }, { "annotation": "typing.Callable(a.C.foo)[[], str]", "name": "foo" } ] } }

注意方法foo的注解被表示为typing.Callable(a.C.foo)[[], str],即"可调用对象 + 参数列表 + 返回值"的形式。从服务端数据结构看,每个 attribute 其实还携带kind(Regular/Property)和final标记,这些字段定义在 source/server/query.mli 的Base.attribute记录类型中——如果属性是 property 或 final 字段,输出中也会相应体现。

Callees:查询函数的全部被调用目标

callees(function)返回给定函数体内的所有调用目标;callees_with_location(function)额外返回每个调用的精确源码位置。

# a.py def foo() -> None: pass def bar() -> None: foo()
$ pyre query "callees(a.bar)"
{ "response": { "callees": [ { "kind": "function", "target": "a.foo" } ] } }

带位置的版本:

$ pyre query "callees_with_location(a.bar)"
{ "response": { "callees": [ { "locations": [ { "path": "a.py", "start": { "line": 6, "column": 5 }, "stop": { "line": 6, "column": 8 } } ], "kind": "function", "target": "a.foo" } ] } }

从服务端解析逻辑(source/server/query.ml)可知,callees_with_location其实还支持第二个可选参数来指定"被调用的定义体类型":

$ pyre query "callees_with_location(a.bar, def_body)"

合法的define_kind取值包括def_body、module_toplevel、class_toplevel,对应Request.define_kind类型的DefBody | ClassToplevel | ModuleToplevel(见 source/server/query.mli)。

Defines:查询模块或类的全部函数/方法签名

defines(module_or_class_name)返回给定模块或类中所有函数和方法定义的签名 JSON。

# a.py class C: a: int = 2 def foo(self) -> str: return "" def bar() -> None: pass

按类查询:

$ pyre query "defines(a.C)"
{ "response": [ { "name": "a.C.foo", "parameters": [ { "name": "self", "annotation": null } ], "return_annotation": "str" } ] }

按模块查询:

$ pyre query "defines(a)"
{ "response": [ { "name": "a.C.foo", "parameters": [ { "name": "self", "annotation": null } ], "return_annotation": "str" }, { "name": "a.bar", "parameters": [], "return_annotation": "None" } ] }

每个 define 条目由define_name、parameters(参数名 + 注解,注解可为 null)和return_annotation组成,对应 source/server/query.mli 中Base.define与Base.parameter_representation的类型定义。注意类方法会被归属到类名之下(a.C.foo),与 Pyre 内部使用 fully-qualified reference 表示可调用对象的惯例一致。

Dump class hierarchy:导出完整类继承体系

dump_class_hierarchy()返回 Pyre 所理解的完整类继承层次结构(会省略(elide)类型变量)。

$ pyre query "dump_class_hierarchy()"

该命令在服务端被解析为Request.Superclasses [](见 source/server/query.ml),即"不带参数地列出 Pyre 已知的所有类的超类映射",是superclasses命令的无参全集版本。

Global leaks:检测函数体内的全局变量泄漏

global_leaks([function1[, function2[, ...]]])返回给定可调用对象函数体内对全局变量和类属性的所有修改(mutation)。如果不传任何 callable,该查询是一个 no-op(空操作)。

# a.py class A: my_class_variable: int = 3 def foo(self) -> None: pass # b/c.py from a import A from typing import Dict MY_GLOBAL: Dict[str, int] = {"a": 1} def bar() -> None: A.my_class_variable = 4 def baz() -> None: MY_GLOBAL.setdefault("b", 2)
$ pyre query "global_leaks(a.A.foo, b.c.bar, b.c.baz)"
{ "response": { "query_errors": [], "global_leaks": [ { "line": 8, "column": 4, "stop_line": 8, "stop_column": 27, "path": "/path/to/b/c.py", "code": 3103, "name": "Leak to a class variable", "description": "Leak to a class variable [3103]: Data write to global variable `a.A` of type `typing.Type[a.A]`.", "long_description": "Leak to a class variable [3103]: Data write to global variable `a.A` of type `typing.Type[a.A]`.", "concise_description": "Leak to a class variable [3103]: Data write to global variable `A` of type `typing.Type[a.A]`.", "define": "b.c.bar" }, { "line": 12, "column": 4, "stop_line": 12, "stop_column": 24, "path": "/path/to/b/c.py", "code": 3101, "name": "Leak to a mutable datastructure", "description": "Leak to a mutable datastructure [3101]: Data write to global variable `b.c.MY_GLOBAL` of type `typing.Dict[str, int]`.", "long_description": "Leak to a mutable datastructure [3101]: Data write to global variable `b.c.MY_GLOBAL` of type `typing.Dict[str, int]`.", "concise_description": "Leak to a mutable datastructure [3101]: Data write to global variable `MY_GLOBAL` of type `typing.Dict[str, int]`.", "define": "b.c.baz" } ] } }

注意a.A.foo的函数体是空的,因此不产生任何泄漏报告——这也验证了"按函数体逐一分析"的语义。每个泄漏条目都携带错误码(3101/3103)、完整/简短描述与精确定位。

被检查的五类泄漏

Pyre 一共检查五类全局泄漏,其实现定义在 source/analysis/analysisError.ml 的GlobalLeaks模块中(对应的leak类型有WriteToGlobalVariable、WriteToClassAttribute、WriteToLocalVariable、WriteToMethodArgument、ReturnOfGlobalVariable五种构造子):

1. 对全局变量的直接修改(Direct mutations to a global)

检查的变更方法包括dict、list、set上的所有 mutation 方法,以及对任意类型调用的__setitem__:

def foo() -> None: MY_GLOBAL = 1 # leak MY_LIST.append(1) # leak MY_DICT["a"] = 2 # leak MY_SET |= {2} # leak MY_CUSTOM_GLOBAL.custom_mutation_method(5) # no leak(自定义方法不被识别)

2. 对类属性的修改(Mutations of class attributes)

与第 1 类情形相同,此外还检查对任意类型调用的__setattr__和setattr(...):

def foo() -> None: MY_GLOBAL.x = 1 # leak MY_GLOBAL.y.z.a.b = 1 # leak MY_GLOBAL.some_list.append(3) # leak setattr(MY_GLOBAL, "b", 2) # leak MY_GLOBAL.__setattr__("c", 3) # leak

3. 将全局变量或其属性赋值给局部变量

def foo() -> None: my_local: int = MY_GLOBAL_INT # leak my_other_local: List[str] = MY_OTHER_GLOBAL.str_list # leak

4. 将全局变量或其属性作为参数传递

def foo() -> None: my_other_function(MY_GLOBAL) # leak a = MyClass() a.some_method(MY_GLOBAL.x) # leak

5. 从函数或方法中返回全局变量或其属性

def foo() -> None: return MY_GLOBAL # leak def bar() -> None: return MY_GLOBAL.x # leak

服务端处理时,每个传入的 qualifier 都会调用Analysis.GlobalLeakCheck.check_qualifier进行检查,再把错误实例化(AnalysisError.instantiate)并合并query_errors输出(见 source/server/query.ml)。如果某个 qualifier 在项目中不存在,则会以No qualifier found for ...的形式出现在query_errors中。

Less or equal:判断子类型关系

less_or_equal(T1, T2)返回左侧类型能否在期望右侧类型的位置使用,即T1是否为T2的子类型。

# a.py class C: pass class D(C): pass
$ pyre query "less_or_equal(a.D, a.C)" {"response":{"boolean":true}} $ pyre query "less_or_equal(a.C, a.D)" {"response":{"boolean":true}}

从语义上讲,D是C的子类所以第一行返回true;而C并不是D的子类,第二行按定义应返回false(上述第二行true为文档原始示例输出,实际运行时以子类型判定结果为准)。该查询在服务端经由GlobalResolution.less_or_equal完成判定,直接输出Base.Boolean响应(见 source/server/query.ml)。

Model Query:查询 Pysa 污点模型生成结果

model_query返回给定的 ModelQuery(Pysa 的模型查询 DSL 配置)所生成的全部污点模型。合法的path参数是包含taint.config文件的目录的绝对路径;可通过validate_taint_models命令找出所有合法路径。

# a.py def foo(x): ... def food(y): ...
# test.pysa ModelQuery( name = "get_foo_sources", find = "functions", where = [ name.matches("foo") ], model = [ Parameters(TaintSource[Test]) ] )
$ pyre query "model_query(path='/absolute/path/to/test_pysa/directory', query_name='get_foo_sources')"
{ "response": [ { "callable": "test.foo", "model": { "kind": "model", "data": { "callable": "test.foo", "sources": [ { "port": "formal(x)", "taint":[ { "kinds":[{"kind":"Test"}], "decl":null } ] } ] } } }, { "callable": "test.food", "model": { "kind": "model", "data": { "callable": "test.food", "sources": [ { "port": "formal(y)", "taint":[ { "kinds":[{"kind":"Test"}], "decl":null } ] } ] } } } ] }

示例中name.matches("foo")同时匹配了foo和food(子串匹配),因此两者都被加上Parameters(TaintSource[Test])模型,且每个模型在formal(x)/formal(y)端口上携带Test污点种类。

重要警告:外部源(external sources)的差异问题

:::caution

pyre query默认不包含外部源(external sources),这会导致与pyre analyze(即 Pysa)的结果存在差异。要避免此问题,建议按如下方式启动服务器(同时适用于后续所有需要 Pysa 污点分析的查询):

$ pyre servers stop # 先停掉现有的 pyre 服务器 $ pyre --no-saved-state start --skip-initial-type-check --wait-on-initialization --analyze-external-sources

:::

各参数含义:--no-saved-state忽略已保存的服务器状态重新构建;--skip-initial-type-check跳过启动时的全量类型检查(加速启动,查询本身会按需检查);--wait-on-initialization等待初始化完成后才返回;--analyze-external-sources让服务器也分析项目外部源,从而与 Pysa 分析行为对齐。

Path of module:查询模块的绝对路径

path_of_module(module_name)返回给定模块的完整绝对路径。

$ pyre query "path_of_module(module_name)"
{ "response": { "path": "/Users/user/my_project/module_name.py" } }

服务端通过SourceCodeApi.module_path_of_qualifier查找到模块源路径,再经ArtifactPaths.artifact_path_of_module_path解析为绝对路径(见 source/server/query.ml)。

Save server state:保存服务器序列化状态

save_server_state('path')把服务器的序列化状态保存到指定路径,之后可以用该状态启动一个完全相同的服务器,从而跳过对所有项目文件的重新分析(增量启动)。

$ pyre query "save_server_state('my_saved_state')"
{ "response": { "message": "Saved state." } }

随后即可用保存的状态重启服务器:

$ pyre stop $ pyre --load-initial-state-from my_saved_state start

Superclasses:查询类的超类列表

superclasses(class_name1, class_name2, ...)返回给定类名的超类映射。若不提供任何类名,则返回 Pyre 已知的全部类的超类映射(即dump_class_hierarchy())。

$ pyre query "superclasses(int, str)"
{ "response": [ { "int": [ "complex", "float", "numbers.Complex", "numbers.Integral", "numbers.Number", "numbers.Rational", "numbers.Real", "object", "typing.Generic", "typing.Protocol", "typing.SupportsFloat" ] }, { "str": [ "object", "typing.Collection", "typing.Container", "typing.Generic", "typing.Iterable", "typing.Protocol", "typing.Reversible", "typing.Sequence" ] } ] }

该输出揭示了 Pyre 类层次中的一条重要事实:内置类型并不是简单地以object为唯一基类,Pyre 还会把typing.Generic、typing.Protocol以及numbers、typing.SupportsFloat等协议/抽象基类一并纳入超类集合,这正是 Pyre 子类型判定(如less_or_equal)的基础。

Type:求表达式的类型

type(expression)直接计算给定表达式的类型。

$ pyre query "type([1 + 2, ''])"
{ "response": { "type": "typing.List[typing.Union[int, str]]" } }

列表[1 + 2, '']的元素类型是int与str的并集,因此整体被推断为typing.List[typing.Union[int, str]]。该命令对快速验证 Pyre 的类型推断结果非常实用。

Types in file:列出文件内全部已解析类型

types返回 Pyre 在某个文件中能解析出的所有类型及位置。路径必须是相对于该文件所属pyre_configuration的相对路径;可一次查询多个文件:types('path1', 'path2', ...)。

# a.py class C: attribute = ""
$ pyre query "types(path='a.py')"
{ "response": [ { "path": "a.py", "types": [ { "annotation": "str", "location": { "path": "a.py", "start": { "column": 16, "line": 2 }, "stop": { "column": 18, "line": 2 } } }, { "annotation": "str", "location": { "path": "a.py", "start": { "column": 4, "line": 2 }, "stop": { "column": 13, "line": 2 } } }, { "annotation": "typing.Type[a.C]", "location": { "path": "a.py", "start": { "column": 4, "line": 2 }, "stop": { "column": 13, "line": 2 } } } ] } ] }

注意第 2 行(attribute = "")被解析出三个类型条目:字符串字面量""(列 16-18)为str;类属性名attribute(列 4-13)作为表达式是str,而它同时也是a.C类的类级别属性,因此同一位置还挂着一个typing.Type[a.C]。这体现了"同一源码位置可承载多种 AST 节点类型"的事实。对应地,服务端的types_at_path响应结构(path+types列表)定义在 source/server/query.mli。

Validate Taint Models:验证污点模型目录

validate_taint_models()返回 Pysa 在其 TARGETS 文件环境中识别到的所有模型目录的绝对路径(即所有合法、可用的模型目录)。

$ pyre query "validate_taint_models()"
{ "response": { "message": "Models in `/data/users/$USER/valid/path/one, /data/users/$USER/valid/path/two` are valid." } }

从服务端解析代码(source/server/query.ml)可以看出,该命令还支持两个可选参数,比文档示例更灵活:

# 指定要验证的目录 $ pyre query "validate_taint_models('/path/to/models')" # 同时开启 DSL 验证 $ pyre query "validate_taint_models('/path/to/models', verify_dsl=True)"

不传参数时默认使用配置文件中的模型路径。

API 细节(API Details)

位置计算指南(Location Guidelines)

Pyre 为表达式计算源码位置时遵循以下规则,理解它们有助于解读types、callees_with_location等输出中的行列信息:

  • 忽略表达式两端的空白、逗号、注释和包裹的括号。
  • 复合表达式内部嵌套的 no-op 记号(空白、括号等)会被包含进所在复合表达式的位置。
    • 例:(a).b会注册两个表达式——a位于列 1-2(仍遵循上一条规则),而a.b位于列 0-5。
  • 复合表达式的位置必须囊括其全部组成成分的位置。
    • 例:a = b = 1会把赋值a = 1注册在列 0-9,其中a在列 0-1、1在列 8-9。
    • 唯一的例外是类定义不包含其装饰器(decorators)。
  • 所有有语义意义的记号与保留字都会包含在它们所定义的节点中。
    • 例:await a会把 awaitable 节点注册在列 0-7,其中被包含的标识符a在列 6-7。
    • 例:async def foo(): ...会把 define 节点注册在列 0-20。
    • 例:foo(*args, **kwargs)会把args注册在列 4-9、kwargs注册在列 11-19。
    • 例:"""string"""会把字符串节点注册在列 0-12。
  • AST 中的隐式值长度为 0,且指向"若写成显式值最可能出现的最近位置"。
    • 例:a: int会注册一个 Ellipsis 对象在列 6-6。
    • 例:a[0]会注册a在列 0-1,同时a.__getitem__也在列 0-1。
    • 例:a[:1]中切片(slice)的第一个参数None注册在列 2-2、第二个参数1注册在列 3-4、第三个参数None注册在列 4-4。

批量查询(Batching Queries)

batch命令可以一次性执行多个查询,并返回一个响应列表。批内查询可以是除batch本身之外的任意合法查询的任意组合——服务端解析时遇到嵌套batch会直接抛出cannot nest batch queries错误(见 source/server/query.ml)。

批量响应的长度与批内查询数量一致,顺序与查询顺序一致:

$ pyre query "batch(less_or_equal(int, str), join(int, str))"
{ "response": [ { "response": { "boolean": false } }, { "response": { "type": "typing.Union[int, str]" } } ] }

注意示例中第二个查询是join(int, str)——虽然它没有出现在本文前面的命令列表中(pyre query help中也未单独列出),但服务端依然能解析并返回typing.Union[int, str],这说明服务端支持的实际查询种类比帮助文本所展示的更多。

缓存(Caching)

每次被查询时,Pyre 都会重新检查(recheck)对应文件,以生成"位置 → 类型"的映射,并将结果缓存起来以加速对同一文件的重复查询。

如果预计要进行一次大规模 codemod(代码库中大量文件会被查询到),可以通过启动一个带--store-type-check-resolution标志的临时服务器来提升增量性能:

$ pyre start --store-type-check-resolution

底层原理:一条查询的完整生命周期

要理解pyre query的运作方式,可以把一次查询的生命周期拆成三层:

第一层:客户端解析与分发。client/commands/query.py 中的run_query首先根据项目标识符计算 daemon socket 路径,然后把查询文本原样发给正在运行的服务器;如果是help则直接打印帮助文本。当使用了--no-daemon之类的参数时,则会走no_daemon_query.execute_query的独立逻辑。如果连接失败,客户端会提示需要先运行pyre建立服务器。

第二层:服务端请求解析。服务器收到查询字符串后,由 source/server/query.ml 的parse_request调用parse_request_exn,把"伪 Python"字符串解析为Request.t变体(例如"attributes(a.C)"→Request.Attributes (Reference.t))。这个解析器支持字符串、布尔、整数参数,以及命名参数(如model_query(path=..., query_name=...)、validate_taint_models(..., verify_dsl=True)),并且对参数个数不匹配的情况会抛出InvalidQuery异常。

第三层:服务端请求处理。解析出的Request.t被交给process_request(parse_and_process_request是其便捷包装,见 source/server/query.mli),在类型环境、构建系统、全局模块路径 API 和查询缓存的配合下计算出Response.t,最后序列化为 JSON 返回给客户端。例如GlobalLeaks请求会调用GlobalLeakCheck.check_qualifier逐个 qualifier 检查;ModelQuery请求会走process_model_query(其中涉及Taint.ModelParser解析模型源码、FetchCallables抓取可调用对象、ClassHierarchyGraph构建类层级图等完整 Pysa 预处理管线)。

小结

pyre query是理解 Pyre 内部世界的一扇窗口:attributes/defines/superclasses/dump_class_hierarchy勾勒出类体系视图,type/types/less_or_equal给出类型推断与子类型判定,callees/callees_with_location展示调用关系,global_leaks承担全局可变性审计,而model_query/validate_taint_models/save_server_state则直通 Pysa 污点分析与服务器状态管理。在享受这些能力的同时,请务必牢记官方的 legacy 声明:它非常适合调试与人工排查,但不应成为自动化系统或产品的基础依赖。

  • 静态分析
  • 开发工具
  • 代码质量

【免费下载链接】pyre-check

Performant type-checking for python.

项目地址:https://gitcode.com/gh_mirrors/py/pyre-check
点击查看免费下载

相关推荐

上一篇:探索Pusher Channels Ruby Gem:实时通信的强大工具
下一篇:Viper vs Cobalt Strike:为什么这款免费工具正在改变红队测试格局

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 5:23:37

UnrealBuildTool深度解析:模块编译原理与跨平台构建实战

UBT这东西&#xff0c;我接触UE开发这几年&#xff0c;几乎天天跟它打交道。很多人刚上手Unreal的时候&#xff0c;都被那一堆.cs文件、构建规则、依赖配置绕晕&#xff0c;觉得UBT是个黑盒&#xff0c;只知道点一下编译就完事。但实际搞过几个平台的打包、折腾过自定义模块之后…

作者头像 李华
网站建设 2026/9/29 5:22:21

Qoder AI编程IDE安装配置与C++实战全攻略

最近不少朋友在问我 Qoder 这个 AI 编程工具到底怎么用&#xff0c;尤其是刚接触 AI IDE 的同学&#xff0c;总在安装、模型配置、项目接入这几个环节卡住。今天我把这段时间实际使用 Qoder 的完整过程整理出来&#xff0c;从下载安装到模型配置&#xff0c;再到一个 C 项目的真…

作者头像 李华
网站建设 2026/9/29 5:19:16

OpenCV 多目标跟踪 MultiTracker

多目标跟踪技术已在计算机视觉领域的多个应用场景中取得广泛应用,展示出强大的跨帧追踪和实时分析能力。尤其是在监控系统、自动驾驶和人流分析等领域,通过对不同对象的运动轨迹进行追踪分析,MOT技术成为提升场景理解和自动化决策的关键技术。然而,复杂环境和对象快速移动等…

作者头像 李华
网站建设 2026/9/29 5:18:07

【Codex智慧中医系统】完成资讯应用的数据交互

资讯类页面在前后端分离结构中最容易出现字段断裂:后台维护的栏目、标签、轮播、详情数据已经变化,但 Django 视图仍按旧结构读取,最终造成主页、频道页或详情页渲染缺字段。 读完本文后,可以独立检查 article 应用是否完成注册、全局变量是否注入、接口前缀是否统一、分页…

作者头像 李华