mypy: A Quick Look into Static Type Checking in Python
Tags: python, mypy, types, plugins
Published: 2018-05-01 14:06:16

---
title: mypy: A Quick Look into Static Type Checking in Python
published: true
description: Mypy is an experimental optional static type checker for Python that aims to combine the benefits of dynamic (or "duck") typing and static typing. 
tags: python, mypy, types, plugins
---

I'm tired of people wrongly implement some class, not according to the base class interface, like adding new positional argument not defined in the base class method. This usually happened when you have plugin pattern where multiple backend or implementation exists for the given functionality. And each backend was developed by separate team/developer.

Or having to write tests just to make sure correct parameters being passed to function. Tests should be written to verify the logic, not the language syntax or semantic.

The first thing I want is something that can verify method signature is according to the base class specification.

https://medium.com/@ageitgey/learn-how-to-use-static-type-checking-in-python-3-6-in-10-minutes-12c86d72677b

Let see the following simple example:-

```python
class A():
    def __init__(self, x):
        self.x = x

    def send(self, msg):
        pass

class B(A):
    def send(self, msg):
        return True
```

The problem with above code is that it possible for other developer (or team) to implement `send()` method in `B` class in a way that incompatible with other A subclass. For example by adding extra parameter:-

```python
class A():
    def __init__(self, x):
        self.x = x

    def send(self, msg):
        pass

class B(A):
    def send(self, msg, extra):
        return True
```

This is possible to go unnoticed until you need to plugin another subclass of `A` to replace `B` but that implementation seem not compatible as it doesn't accept the `extra` parameter. This can be avoided if we have a way to type check the method signature to make sure it conform to the base class specification.

```python
import abc

class A(abc.ABC):
    def __init__(self, x: int) -> None:
        self.x = x     # Attribute x of type int

    @abc.abstractmethod
    def send(self, msg: str) -> bool:
        pass

class B(A):
    def send(self, msg: str) -> bool:
        return True

b = B(1)
b.x = 2       # OK
b.send('hello')
```

Now if someone try to implement `B` by adding additional parameter to `send()`, such as:-

```python
import abc

class A(abc.ABC):
    def __init__(self, x: int) -> None:
        self.x = x     # Attribute x of type int

    @abc.abstractmethod
    def send(self, msg: str) -> bool:
        pass

class B(A):
    def send(self, msg: str, extra: str) -> bool:
        return True

b = B(1)
b.x = 2       # OK
b.send('hello', 'extra')
```

Running that through mypy will give us error:-

```
base.py:12: error: Signature of "send" incompatible with supertype "A"
```

Nice, now we solved problem mentioned at the beginning of this article.

Another common issue is mismatch in the return value. For example:-

```python
import abc

class A(abc.ABC):
    def __init__(self, x: int) -> None:
        self.x = x     # Attribute x of type int

    @abc.abstractmethod
    def send(self, msg: str) -> bool:
        pass

class B(A):
    def send(self, msg: str) -> bool:
        return 'hello'

b = B(1)
b.x = 2       # OK
b.send('hello')
```

Above, method `send()` in class A was annotated to return a Boolean but since `B.send()` return a string, mypy would complain:-

```
base.py:13: error: Incompatible return value type (got "str", expected "bool")
```

I can see some potential in mypy here. It can be a good compliment to our tests suite so we don't have to write tests for some cases that can actually being caught by compiler (if we do have one). But unfortunately, mypy does [not yet support namespaced package](https://github.com/python/mypy/issues/1645) and we do use namespaced package a lot.

Another issue we faced was with buildout. To provide type hinting for standard library (which most doesn't have the annotation yet), mypy provide a shadowing library called typeshed. Buildout script define it's own `sys.path` but mypy is ignoring `sys.path` because it need to look in the typeshed first, this mean all the paths set by buildout in our script not usable. We have to specify that as `MYPYPATH` but that still didn't work so I just gave up.