Static Analysis Options for Python

Copyright 2019 Brian Davis - CC-BY-NC-SA

These are some of my notes from researching static analysis options for Python code.

PEPs

PEP484 Type Hints PEP544 Protocols PEP3119 ABCs

Personal Goals for Static Analysis

The dynamic nature of python is a huge benefit for developer productivity (I've found) during the prototyping stage. Where I feel it's lacking is when changes are made to a large codebase and bugs can only be discovered by exercising every bit of code. There are classes of fairly simple errors that can/should be caught without having to write any test code.

In no particularly order and not exhaustive...

  1. If a function returns more than one sort of thing (really helpful for productivity) but the calling code doesn't properly handle both types.
  2. Code branch B relies on object A existence but B is reachable w/o A being created.
  3. Dictionary key is used but does not exist. Misspelled or dynamically generated.
  4. Input data cannot be handled (edge case not foreseen). hmmm... What was I thinking here?
  5. Syntax error, name misspelled in rarely used branch.

Some of these errors are hard to detect but I think they should be possible to detect with static analysis. Even if the compiler merely warned of possible problem area (dynamic dictionary keys for example) that would alert the programmer to add extra checks and tests.

Mypy

Mypy

Mypy ignores code in functions whose signature does not include type hints, even if the code in the function body could be type checked.

Mypy supports Nominal subtyping, basically using inheritance to check if classes are compatible. Structural subtyping says that a class is compatible if it has all the members/methods of a protocol class. The typing module has a few protocols already defined. Or subclass typing_extensions.Protocol.

If a protocol method returns itself (or compatible type) use single quotes. (ex. -> 'MyProtocol':)

How to make Mypy validate that a class implements a protocol? That does not seem to be the intent. ABCs specifically don't check for unimplemented methods because a subclass of an ABC may also be an ABC.

While PEP544 seems to indicate that checkers could validate classes that claim to implement a protocol, mypy does not appear to.

If I create a class that explicitly implements a protocol but fails to implement a method, mypy won't catch that, even if my code tries to use said method. It will catch this situation if the class implicitly implements said protocol. (This seems odd to me...)

python-interface

python-interface will raise very nice exceptions when a class is declared to implement an interface and fails to do so. Appears to be incompatible with Mypy however and does not check type annotations itself.

pytype

pytype is a type checker from Google. pip install downloaded a ton of stuff and failed on my Windown 7 machine.

Other things to investigate

blog posts that I found very informative

https://glyph.twistedmatrix.com/2020/07/new-duck.html https://glyph.twistedmatrix.com/2021/03/interfaces-and-protocols.html