mypy可以报告文件的所有数据类型吗?怎么样?

问题描述 投票:0回答:1

我正在探索创建一些 IDE-mypy 集成。显然我可以运行 mypy 来查找输入错误。我还可以使用

dmypy
inspect
来查找特定行和列的特定变量的类型。我还可以使用
reveal_type()
来检查特定变量。但是我怎样才能列出一个文件的每条类型信息呢?实际上在所有东西上运行
reveal_type()
。例如,这将包括每个变量及其类型的列表。 mypy 可以这样做吗?如果是这样,怎么样?

mypy
1个回答
1
投票

是的,这是可能的,但我不认为 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
© www.soinside.com 2019 - 2024. All rights reserved.