Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type hints: when to annotate

I'm using type hints and mypy more and more. I however have some questions about when I should explicitly annotate a declaration, and when the type can be determined automatically by mypy.

Ex:

def assign_volume(self, volume: float) -> None:
    self._volume = volume * 1000

Should I write

self._volume: float = volume *1000

In this case?

Now if I have the following function:

def return_volume(self) -> float:
        return self._volume

and somewhere in my code:

my_volume = return_volume()

Should I write:

my_volume: float = return_volume()
like image 267
JPFrancoia Avatar asked Jul 06 '18 12:07

JPFrancoia


1 Answers

Mypy (and PEP 484 in general) is designed so that in the most ideal case, you only need to add type annotations to the "boundaries" or "interfaces" of your code.

For example, you basically must add annotations/type metadata in the following places:

  1. The parameter and return types of functions and methods.
  2. Any object fields (assuming the types of your fields are not inferrable just by looking at your constructor)
  3. When you inherit a class. For example, if you specifically want to subclass a dict of ints to strs, you should do class MyClass(Dict[int, str]): ..., not class MyClass(dict): ....

These are all examples of "boundaries" of your code. Type hints on parameter/return types let the caller of the function make sure they're calling it correctly, type hints on fields let the caller know they're using the object correctly, etc...

Mypy (and other PEP 484 compliant tools) will then use that information and try to infer the types of everything else. This behavior is designed to roughly mimic how humans read code: once you know what types are being passed in, for example, it's usually pretty easy to understand what the rest of the code does.

After all, Python is a language that was designed from the start to be readable! We don't need to scatter type hints everywhere to enhance our understanding of what the code does.

Of course, mypy (and other PEP 484-compliant tools) aren't perfect, and sometimes they might not correctly infer what the type of some local variable will be. In that case, you might need to add a type hint to help mypy along. Ethan's answer gives a good overview of some common cases to watch out for. (Interestingly, these cases also tend to be examples of where a human reader might struggle to understand your code!)

So, to put everything together, the general recommendation is to:

  1. Add type hints to all of the "boundaries" of your code, like function parameters and return types.
  2. Default to not annotating variables. If mypy is unable to infer what type some variable should be, add an annotation to help it.
  3. If you find yourself needing to annotate lots of variables to make mypy happy, consider refactoring your code. If mypy is getting confused easily, a human reader is also likely to get confused easily.

So, to go back to your examples, you would not add type hints in either case. Both a human reader and mypy can tell that your _volume field must be a float: it's immediately obvious that must be the case since the parameter is a float and multiplying a float by an int will always produce another float.

Similarly, you would not add an annotation to your my_volume variable. Since return_volume() has type hints, it's trivially easy to see what type it's returning and understand that my_volume is of type float. (And if you make a mistake and accidentally think it's something other then a float, then mypy will catch that for you.)

like image 196
Michael0x2a Avatar answered Nov 19 '22 22:11

Michael0x2a