我正在探索创建一些 IDE-mypy 集成。显然我可以运行 mypy 来查找输入错误。我还可以使用
dmypy
和 inspect
来查找特定行和列的特定变量的类型。我还可以使用 reveal_type()
来检查特定变量。但是我怎样才能列出一个文件的每条类型信息呢?实际上在所有东西上运行reveal_type()
。例如,这将包括每个变量及其类型的列表。 mypy 可以这样做吗?如果是这样,怎么样?
是的,这是可能的,但我不认为 mypy 可以很容易地做到这一点。您还必须决定如何报告信息。
(说)使 mypy 表现得好像
reveal_type
围绕每个 expression 的一种可能方法是执行就地 AST 转换,以在 mypy 开始任何分析之前用 reveal_type(<expression>)
调用包装所有表达式。
以下是一个不完整的插件实现,它将调用
reveal_type
在模块中具有确切名称 my_module_of_interest
的每个表达式。这个插件需要interpreted mypy(卸载mypy然后例如pip install mypy --no-binary mypy
);这是因为 mypy 的大部分核心访问者类在编译后的 mypy 下都不可继承。
IDE lint 的结果如下所示(PyCharm,11086-mypy 插件):
在此模块上运行
:mypy
$ mypy my_module_of_interest.py my_module_of_interest.py:1: note: Revealed type is "Literal['\ndocstring\n']?" my_module_of_interest.py:5: note: Revealed type is "Literal[3]?" my_module_of_interest.py:6: note: Revealed type is "Tuple[Literal[1]?, Literal[2]?, Literal[3]?]" my_module_of_interest.py:6: note: Revealed type is "Literal[1]?" my_module_of_interest.py:6: note: Revealed type is "Literal[2]?" my_module_of_interest.py:6: note: Revealed type is "Literal[3]?" my_module_of_interest.py:8: note: Revealed type is "Tuple[Literal[1]?, Literal[2]?, Literal[3]?, Literal[4]?]" my_module_of_interest.py:8: note: Revealed type is "Literal[1]?" my_module_of_interest.py:8: note: Revealed type is "Literal[2]?" my_module_of_interest.py:8: note: Revealed type is "Literal[3]?" my_module_of_interest.py:8: note: Revealed type is "Literal[4]?" my_module_of_interest.py:12: note: Revealed type is "Literal[8]?" Success: no issues found in 1 source file
mypy.ini
[mypy]
# Relative directory to the plugin module. Must contain the entry
# point `def plugin(version: str) -> type[mypy.plugin.Plugin]`.
plugins = relative/path/to/reveal_type_plugin.py
relative/path/to/reveal_type_plugin.py
from __future__ import annotations
import typing as t
import mypy.nodes
import mypy.plugin
import mypy.treetransform
import mypy.types
if t.TYPE_CHECKING:
import mypy.options
def plugin(version: str) -> type[mypy.plugin.Plugin]:
"""
mypy plugin entry
"""
return RevealTypePlugin
class RevealTypePlugin(mypy.plugin.Plugin):
"""
mypy plugin which reports `reveal_type` on expressions in a module
"""
def get_additional_deps(
self, file: mypy.nodes.MypyFile
) -> list[tuple[int, str, int]]:
"""
Hook in to `get_additional_deps` to transform a module's expressions into
`reveal_type(<expression>)`.
"""
# The test module we're using is called `my_module_of_interest`, with a file
# path of `my_module_of_interest.py`.
if file.fullname == "my_module_of_interest":
transformer: _RevealTypeTransformer = _RevealTypeTransformer()
transformer.test_only = (
True # Needed to allow calling node transforms on a module
)
file.defs = transformer.mypyfile(file).defs
return super().get_additional_deps(file)
class _RevealTypeTransformer(mypy.treetransform.TransformVisitor):
"""
For all expressions except the l-value of assignment statements, transform the
expression into `reveal_type(<expression>)`.
"""
# State variable when set when entering and exiting visitation of an assignment
# statement's l-values
_allow_reveal_type_wrap: bool = True
def visit_assignment_stmt(
self, node: mypy.nodes.AssignmentStmt
) -> mypy.nodes.AssignmentStmt:
"""
Visits assignment statements, disabling expression transformation when visiting
the statement's l-values.
"""
self._allow_reveal_type_wrap = False
new_lvalues: list[mypy.nodes.Lvalue] = self.expressions(node.lvalues)
self._allow_reveal_type_wrap = True
# The following steps are to add and tweak `reveal_type`'s reporting context
# This will recursively add `reveal_type` to compound expressions:
# a = 1, 2 -> a = reveal_type((reveal_type(1), reveal_type(2)))
new_rvalue: mypy.nodes.Expression = self.expr(node.rvalue)
# Shifts the outer `reveal_type(<...>)` node to be at the
# left-hand-side assignment statement:
# a = (reveal_type(1), reveal_type(2))
# vvvvvvv ^ ^ ^
# reveal_type(<tuple>) <tuple>
new_rvalue.set_line(node.lvalues[0])
# At this stage, the `reveal_type` expression is at `a`, but the reporting
# context is at `<tuple>`, which is still on the right-hand-side of the
# assignment.
#
# Wrap the `reveal_type` in another `reveal_type`. This will now report
# properly:
# a = (reveal_type(1), reveal_type(2))
# ^ vvvvvvvvvvvvvvvvvvvv ^ ^
# reveal_type(reveal_type(<tuple>))
new_rvalue_at_lvalue: mypy.nodes.Expression = self._makeRevealTypeNode(
new_rvalue
)
# The rest of this is the same as `super().visit_assignment_stmt`
new: mypy.nodes.AssignmentStmt = mypy.nodes.AssignmentStmt(
new_lvalues,
new_rvalue_at_lvalue,
self.optional_type(node.unanalyzed_type),
)
new.line = node.line
new.is_final_def = node.is_final_def
new.type = self.optional_type(node.type)
return new
def expr(self, expr: mypy.nodes.Expression) -> mypy.nodes.Expression:
"""
Makes a copy of an expression node, wrapping it in `reveal_type(<node>)` if
allowable
"""
new_expr: mypy.nodes.Expression = super().expr(expr)
if self._allow_reveal_type_wrap:
new_expr = self._makeRevealTypeNode(new_expr)
return new_expr
@staticmethod
def _makeRevealTypeNode(expr: mypy.nodes.Expression) -> mypy.nodes.CallExpr:
"""
Turns an expression node into a `reveal_type(<node>)`
"""
callee_ref_node: mypy.nodes.NameExpr = mypy.nodes.NameExpr(name="reveal_type")
callee_node: mypy.nodes.CallExpr = mypy.nodes.CallExpr(
callee_ref_node, [expr], [mypy.nodes.ARG_POS], [None]
)
callee_node.set_line(expr)
return callee_node