Monday, 21 November 2016

Mypy 0.4.6 Released

We’ve just uploaded mypy 0.4.6 to PyPI. This release adds many new features, bug fixes and library stub updates. As always, you can install it as follows, being careful to install mypy-lang, not mypy:

    python3 -m pip install --upgrade mypy-lang

Generic Type Aliases

You can now define type aliases that are themselves generic types. For example, consider this definition of Vector:

    from typing import TypeVar, List

    T = TypeVar('T')

    Vector = Tuple[T, T, T]

Now Vector[float], for example, is equivalent to Tuple[float, float, float]. Generic type aliases can make your type annotations shorter and easier to read. The following example defines two equivalent functions. The first one uses a generic type alias to simplify the signature:

    def negate1(vecs: List[Vector[float]]) -> List[Vector[float]]:
        return [(-x, -y, -z) for x, y, z in vecs]

    def negate2(vecs: List[Tuple[float, float, float]]
               ) -> List[Tuple[float, float, float]]:
        return [(-x, -y, -z) for x, y, z in vecs]

Check out the documentation for more details.

This was contributed by Ivan Levkivskyi and he also implemented the related changes to typing.

Detecting Missing Return Statements

Mypy now can generate an error if a function is missing a return statement, unless the return type is None or Any. Use the --warn-no-return command line option to enable this, or the warn_no_return config file setting. When the option is set, mypy warns about this function, since it doesn’t return anything if the condition is false:

    def f(n: int) -> int:
        if n > 3:
            return n + 1  # Missing return statement

Mypy currently doesn’t warn about empty function bodies or bodies that only have an ellipsis (), since these are sometimes used for abstract methods and aren’t necessarily a problem. The option can generate false positives if you rely on the implicit None return value when execution falls off the end of a function.

This was contributed by Reid Barton.

Improved Type Checking of Import Cycles

Previous versions of mypy were prone to giving errors like ‘Cannot determine type of “foo”’ if your program had import cycles. Another common problem was that innocent-looking changes inside import cycles would cause mypy to emit a new set of unrelated errors.

This release has two changes that should resolve most of these issues. They are a bit technical, and it’s not important to understand how they work in order to enjoy the benefits. Anyway, the two following subsections have a summary for those who are interested in the nitty-gritty internal details. Feel free to skip them. For even more details, have a look at these PRs: #2264 and #2167

Deferred Checking within Import Cycles

Previously mypy type-checked each module only once, a single module at a time. This could be a problem with import cycles. For example, module a could depend on inferred types in b, and b could depend on types of a. No linear ordering of modules would produce clean output, unless the program had type annotations for all the problematic variables — this was only a problem for things whose types weren’t immediately obvious to mypy. Mypy now type-checks an import cycle in two passes. If there is a reference to a variable in another module that mypy hasn’t yet processed, mypy defers the type-checking of the surrounding function, and finally runs another type-checking pass for only deferred functions.

Programs without cycles aren’t affected, since mypy has type-checked single modules in two passes for some time. We’ve now generalized it to multiple modules in a cycle.

Stable Processing Order within Import Cycles

The erratic mypy behavior mentioned earlier was caused by changes in the order of processing modules within a cycle. Mypy goes through each module in a cycle in a linear order, and some errors are only triggered for certain orderings. In previous versions of mypy a small change in a program could trigger big changes in the module processing order. Mypy now better approximates the runtime module initialization order, which is usually pretty stable.

For example, if module a has a statement like from b import f, module b likely will be initialized before module a at runtime, and so mypy will type-check b before a. On the other hand, if an import statement is within an if TYPE_CHECKING: block, mypy will not use this heuristic, since typing.TYPE_CHECKING is false at runtime and the import statement won’t affect runtime behavior.

Self Types (Experimental)

[This feature is experimental — the implementation still has significant limitations or bugs, and the feature may change in future mypy versions in incompatible ways.]

Sometimes you have a method that returns a value with exactly the same type as self. Previously there was no way to do this. Now you can annotate the self argument with a type variable to express this. Here’s an example taken from the documentation:

    from typing import TypeVar

    T = TypeVar('T', bound='Shape')

    class Shape:
        def set_scale(self: T, scale: float) -> T:
            self.scale = scale
            return self

    class Circle(Shape):
        def set_radius(self, r: float) -> 'Circle':
            self.radius = r
            return self

    class Square(Shape):
        def set_width(self, w: float) -> 'Square':
            self.width = w
            return self

    circle = Circle().set_scale(0.5).set_radius(2.7)  # type: Circle
    square = Square().set_scale(0.5).set_width(3.2)  # type: Square

By declaring self as a type variable, the set_scale can return a Circle when called on a Circle object and a Square when called on a Square, while only having a single method definition in the Shape base class.

For a class method, you can also now use Type[T] in a similar way as the annotation for the cls argument.

This feature was contributed by Elazar Gershuni.

Type Applications

Mypy now supports a type application syntax for user-defined generic classes. For example, consider a generic Stack class:

    from typing import TypeVar, Generic

    T = TypeVar('T')

    class Stack(Generic[T]):
        ...

When constructing a Stack instance, you can use the type application syntax Stack[<type>] to specify the type arguments. This constructs a Stack[int] instance using a type application:

    stack = Stack[int]()

Previously you had to use a type annotation, which is more verbose:

    stack = Stack()  # type: Stack[int]

This is now practical since the latest typing implementation caches the result of Stack[int], so it will be quick enough even for a frequently called function. This was actually supported in early mypy releases, but it has been unsupported for a while, primarily due to performance concerns.

You still have to use the type annotation syntax for standard-library classes such as list and Queue since these classes don’t support the indexing operator.

This was contributed by Ivan Levkivskyi. He also implemented the related changes to typing.

New NamedTuple Syntax (Python 3.6)

You can use a new syntax for named tuples with Python 3.6b1 or newer:

    from typing import NamedTuple

    class Point(NamedTuple):
        x: int
        y: int

    p = Point(x=1, y=2)

Run mypy with --fast-parser --python-version 3.6 to use this. You’ll need to install a recent version of typed_ast (pip3 install --upgrade typed_ast).

This was contributed by Ivan Levkivskyi.

Config File Changes

The filename patterns in the mypy configuration file were changed to module name patterns. You’ll have to update your config files if you use this feature.

Here’s an example mypy.ini file that rejects functions without a type annotation in the frobnicate package but allows them elsewhere:

    [mypy-frobnicate.*]
    disallow_untyped_defs = True

You can configure additional module search path entries in the mypy configuration file through the mypy_path configuration file option. This can be useful with local stub files stored in a separate directory, for example. Previously you had to use the MYPYPATH environment variable. mypy_path was contributed by Filip Figiel.

New Command Line Options

  • Add --cobertura-xml-report for outputting Cobertura XML reports with type checking coverage information (Roy Williams).
  • Add support for using a custom typeshed directory (--custom-typeshed-dir).
  • Add --junit-xml=PATH option for generating JUnit XML files with type checking results.
  • Add --find-occurrences that finds all references to a class member using static type information (experimental).
  • Stubgen now supports --fast-parser (Ivan Levkivskyi).

Other Changes in This Release

  • Improvements to --strict-optional (please go ahead and try it — it now works pretty well).
  • Allow subclassing Tuple[...] outside stubs again (Ivan Levkivskyi).
  • Show column number for reveal_type() expressions.
  • Various updates to the bundled typing module.
  • Support conditional import in functions/classes (Mark Laws).
  • Support matrix multiplication operator @ (Elazar Gershuni).
  • Several miscellaneous bug fixes.
  • Several documentation improvements.
  • Lots of updates to the library stubs in typeshed.

Acknowledgements

Thanks to all mypy contributors who contributed to this release:

  • Alex Jurkiewicz
  • Byungwoo Jeon
  • Calen Pennington
  • Chris Lamb
  • David Foster
  • Elazar Gershuni
  • Filip Figiel
  • Herbert Ho
  • Ivan Levkivskyi
  • Kevin Yap
  • Mark Laws
  • Reid Barton
  • Roy Williams
  • Ryan Gonzalez
  • Ulric Aleksandrov

Additional thanks to these contributors to typeshed:

  • Alvaro Caceres
  • Calen Pennington
  • David Percy
  • Dima Gerasimov
  • Eklavya Sharma
  • Evan Hubinger
  • Gerhard Hagerer
  • Hong Minhee
  • Jakub Stasiak
  • Jon Dufresne
  • Jordan Pittier
  • Joshua Smock
  • Kai Lautaportti
  • Matthias Kramm
  • Onno Kortmann
  • Reiner Gerecke
  • Ruud van Asseldonk
  • Ryan C. Thompson
  • Sebastian MeƟmer
  • TrueBrain
  • Xavier Mehrenberger
  • Yegor Roganov
  • Joseph H Garvin
  • nobuggy
  • paavoap

— Jukka (on behalf of the rest of the mypy team: Guido, David and Greg)