pytype

repository·main·Indexed 26 days ago

https://github.com/google/pytype

A static type checker for Python that uses bytecode-based inference. It includes tools such as analyze_project for analyzing Python code, merge-pyi for copying type annotations from stub files, and the pytype_extensions package. The project supports Python versions up to 3.12 and provides a developer-focused abstract value system for modeling Python objects and processing PEP 484 type annotations.

Tokens
23.2K
Snippets
65
Records
170
Agent score
90%

What's inside pytype

  1. Understand pytype non-standard typing behaviors

    main

    pytype includes several non-standard behaviors that differ from other type checkers:

    • String Iteration Protection: pytype forbids str from matching an iterable of strs to catch accidental string iteration bugs.
    • pytype_extensions: A namespace containing various user-contributed extensions.
    • Relaxed Null/Ellipsis Assignments: pytype allows type-annotated variables to be assigned to None or ... without explicitly including them in the annotation (e.g., x: str = None or x: str = ... are valid).
  2. Check Python version compatibility for pytype

    main
    Pytype is entering a maintenance phase. The last supported Python version for pytype is Python 3.12. Users should plan to migrate to alternative Python typing solutions if they require support for Python versions newer than 3.12.
  3. Understand the pytype Typegraph and Control Flow Graph (CFG)

    main

    pytype uses a Control Flow Graph (CFG) to represent the execution paths of a program. This graph is used to track variable types and scope during analysis.

    Key concepts:

    • CFGNode: Represents a single statement or a group of opcodes in the Python program. Nodes can have conditions (e.g., the result of an if statement) that restrict which paths can be taken.
    • Variable: Tracks type information for entities like simple variables, function arguments, classes, or modules.
    • Binding: Associates a Variable with a specific value at a particular CFGNode. A variable may have multiple bindings representing all possible values it could hold at that point in the program.
    • Origin: Explains how a Binding is constructed. It consists of a CFGNode and a Source Set (a collection of Bindings used to derive the new one, such as in y = m * x + b).
    • Visibility: A Binding is considered 'visible' at a node if there is a valid path in the CFG from the node where the Binding originated to the current node, respecting any associated conditions.
  4. Understand how pytype processes type annotations

    main

    Pytype processes PEP 484 type annotations (type hints) by parsing them as part of the regular bytecode VM. Unlike type comments, annotations are compiled by the interpreter using SETUP_ANNOTATIONS and STORE_ANNOTATION opcodes.

    Pytype maintains an abstract.AnnotationsDict (equivalent to Python's __annotations__) to store these annotations in locals for function variables or in __dict__ for class members. This dictionary is updated via:

    • vm._record_local(): Records an annotation on a local variable.
    • vm._apply_annotation(): Applies an explicit AnnotationsDict (e.g., for class objects).
    • byte_STORE_ATTRIBUTE: Handles attribute assignments that haven't been recorded as class-level annotations.
  5. Understand pytype's Special Builtins mechanism

    main

    Pytype uses special builtins to handle Python functions that have complex type-level side effects (like metaprogramming) or type effects that depend on argument values (e.g., super()). While standard functions are modeled via signatures, special builtins allow pytype to directly manipulate abstract values.

    Note that these special functions still require type signatures in builtins.pytd to interoperate with the rest of pytype.

  6. Understand pytype's bytecode compilation process

    main

    Pytype analyzes Python code by following these steps:

    1. Compilation: The Python source is compiled into bytecode using the appropriate interpreter (the host interpreter if versions match, or a target-version interpreter if they differ).
    2. Disassembly: The bytecode is disassembled into Opcodes, which are pytype's internal representation of Python bytecode instructions.
    3. Interpretation: Pytype's vm.py/VirtualMachine interprets these Opcodes, manipulating types rather than actual values.
  7. Understand pytype's core architecture

    main

    pytype operates using a shadow bytecode interpreter that traces through a program's bytecode. It mimics the CPython interpreter but tracks types instead of values.

    Key architectural components include:

    • Virtual Machine (VM): The bytecode interpreter that traces the program.
    • Typegraph: A graph mapping the flow of types through a program. Each Node in the graph roughly correlates to a single statement.
    • Variable: Tracks type information for a program variable. A variable has one or more Bindings, which associate the variable with Abstract Values (also called Data) at specific nodes.
    • PyTD: An AST representation used to serialize and deserialize top-level definitions. This allows pytype to analyze programs that depend on previously analyzed files by reading the PyTD files and converting nodes back into abstract values.
  8. Compare pytype semantics with mypy

    main
    Pytype's typing semantics are a property of the type checker, not the Python language itself. While pytype aims for consistency with mypy, pyre, and pyright, it may differ in areas not formally covered by PEPs. A primary driver for these differences is pytype's design goal to avoid breaking existing, unannotated code that follows valid Python idioms.
  9. Analyze Python projects with analyze_project

    main
    The analyze_project tool is used to analyze one or more files or directories of Python code. It automatically handles dependency ordering and the generation of .pyi files during the analysis process. For general usage and installation instructions for pytype, refer to the main project README.
  10. Understand PyType Declaration (PyTD) and Mutations

    main

    PyType uses an extended .pyi format called PyType Declaration (PyTD) to support mutations. A mutation allows describing how an unannotated parameterized class's contained type changes during an operation. In PyTD, mutations are expressed via assignments to self within a method body.

    Example of a mutation in dict.update:

    class dict(Dict[_K, _V]):
      def update(self, other: dict[_K2, _V2]) -> None:
        self = dict[_K | _K2, _V | _V2]