Reading mypy error messages

By this point you've probably used mypy a fair bit, maybe even outside of the course. And you've probably seen a bunch of these by now:

mycode.py:2: error: No return value expected
mycode.py:5: error: Incompatible types in assignment (expression has type "str", variable has type "int")
mycode.py:7: error: Function is missing a return type annotation

And with the practice, you should already be able to tell exactly what each of these errors mean, maybe even predict what the code looks like.

Here's the actual code:

def foo() -> None:
    return 42

bar = 10
bar = 'baz'

def quux():
    return {'x'}
  • The first error shows up when you try to return data from a function that is marked to not return anything (-> None).
  • The second error should be an int variable being assigned a string.
  • The third error is a function without a proper type signature.

As you get more and more familiar with mypy errors, you'll start realizing that most of them boil down to just two things:

  • Your code has a type mismatch, eg. mypy expected object of one type but found object of a different type.
  • Mypy is unable to figure out the types in your code, and needs you to add more type information, eg. in a function defintion.

Let's see a couple more examples to explain this in detail:

def print_spendings(purchases: list) -> None:
    total_spendings = 0
    for item, value in purchases:
        print(f"Bought {item} for {value} dollars")
        total_spendings += value

    print("Total spent:", total_spending)

purchases = [
  ("Eggs", 10),
  ("Cheese", 25),
  ("Bacon", 15),
]
print_spendings(purchases)

The second error is easy to understand and fix: We made a typo in the variable name. The first one though, is telling us that mypy needs more information about a function parameter, purchases in this case. By "type parameters" it means the part inside the [] brackets.

We can see that it's a list of 2-tuples, but how would you write that as a type hint? I could tell you, but you can also use reveal_type to figure it out yourself:

purchases = [
  ("Eggs", 10),
  ("Cheese", 25),
  ("Bacon", 15),
]
reveal_type(purchases)

So the type is supposed to be list[tuple[str, int]]. That's how tuple typing works, by the way. Since tuples are essentially a way to represent a collection of data points related to an entity. While other collections usually represent a bunch of items, tuples usually represent a single item.

A good example is sqlite:

>>> for row in cursor.execute('SELECT name, age, bio FROM users'):
...     print(row)
('Joe', 23, 'Hello!')
('Mike', 27, 'Web developer from California.')
>>>

For this reason, tuples take a type parameter for every index inside the tuple. The type of the above tuples would be tuple[str, int, str].

In case you want to make a tuple that contains data like a list, a tuple of many strings for example, you can use the ... syntax, like this:

items: tuple[str, ...] = ("eggs", "cheese", "bacon")


Anyway, you can fix the function signature like so:

def print_spendings(purchases: list[tuple[str, int]]) -> None:

Once you do that, mypy should report no errors.

Let's see another example:

def unique_count(nums: list[int]) -> int:
    """counts the number of unique items in the list"""
    uniques = set()
    for num in nums:
        uniques.append(num)

    return uniques

counts = unique_count([1, 2, 1, 3, 1, 2, 4, 3, 1])
print(counts)

The first error says we need a better type annotation for uniques. Since we're making an empty set, mypy doesn't know what it should contain. Add : set[int] to fix that.

The second error says set doesn't have an append method -- and it's right. Sets don't have .append(), they use .add() to add items. So let's fix that method call as well.

The third error says we're returning a set, when we're supposed to return an integer. The code was supposed to return len(uniques) instead.

With those fixed, the code should run and mypy should be happy.