Featured image of post Type hints in Python 3

Type hints in Python 3

Python has support for type hinting since version 3.5. That’s quite useful, because it makes your code more predictable since you have more information about what types to expect. In this article we will dive deeper into what type hinting exactly is, how it works and how you can use it in Python 3.

With type hinting in Python 3 you can indicate which type a variable has. You can also use it to indicate for functions what the parameter types are and what the return type is. The point of the type hinting in Python is to provide type hints for your code editor. Your IDE can then notify you when your types don’t match. For example, if you pass an int to a method that only accepts a string parameter. This is useful for when you are writing or analysing code.

IntellIJ and PyCharm have support for type hinting since 2015. Visual Studio Code also has support via the plug-in ‘Pylance’ from Microsoft, albeit it has type checking turned off by default. To enable it in that plug-in, you need to go to your extensions and search for ‘Pylance’. Click the cog wheel on the extension page and choose Extension Settings. Scroll down on the page and select basic or strict from the drop-down list at the option Type Checking Mode.
If you want to check the types of your Python scripts when you are running a CI build, then you can use a third-party library like mypy.

The Python interpreter itself doesn’t do anything with the provided type hints. That’s why they are type hints. Your code is executed as normal, even though the types don’t match with the type hints. That makes sense, since type hinting otherwise would break duck typing. The point of duck typing (“If it quacks like a duck, treat it like a duck”) is of course that the types don’t matter.

An example of a type hint in Python 3 looks like this:

def greeting(name: str) -> str:
  return 'Hello ' + name

You can see here that we accept a parameter of the type string and that the function itself also returns a string.
For a list of all types you can use in Python, see the documentation. You’ll also find here the Union type, which you can use to accept combinations of types, as well as list types, TypeVar for generics and more.

Let’s look at some advanced use-cases. For example, we can also create aliasses for existing types:

Vector = list[float]

We can also create whole new types, using Python’s built-in NewType helper:

PostId = NewType('PostId', int)
my_post = PostId(1234)

Another nice feature from type hints is that you can also provide dictionaries with types, using the TypedDict class. You can create a new class that extends the TypedDict class and in there you define the keys with for each key the type of its value.

Duck typing

We’ve already mentioned it briefly earlier, namely Duck typing. Type hinting has special support for duck typing in the form of protocols.

Suppose we have the following duck:

class Duck:
  def quack(self) -> str:
    return "Quack!"

def make_a_sound(duck: Duck) -> None:
  print(duck.quack())

make_a_sound(Duck())

If we want to use your make_a_sound method also for other types, then we don’t have to do anything thanks to duck typing. This is because make_a_sound accepts any class as long as it has a .quack() method. But if we want more certainty and make sure we only call the make_a_sound method with classes that have that method, we can define a protocol like so:

from typing import Protocol

class CanQuack(Protocol):
  def quack(self) -> str:
    pass

class Duck:
  def quack(self) -> str:
    return "Quack!"

class BathtubDuck:
  def quack(self) -> str:
    return "Blub blub"

def make_a_sound(duck: CanQuack) -> None:
  print(duck.quack())

make_a_sound(Duck())
make_a_sound(BathtubDuck())

Here, we define the protocol CanQuack, which contains the method quack(). Our method make_a_sound now accepts objects that conform to this protocol. The type checker will now check whether an object that calls make_a_sound conforms to this protocol. This is called structural subtyping: the type checker checks the internal structure of a class. This means that both are Duck and BathtubDuck don’t have to extend or implement anything. It is only checked if they contain the method quack().

Besides structural subtyping there is also nominal subtyping, in which types you need to explicitly name the types, for example by naming the class that you extend or the interface that your class implements.

You can use typing.Protocol since Python 3.8. For earlier versions of Python 3, you can use the pypi package typing_extensions.Protocol to achieve the same functionality.

Conclusion

We’ve explained the purpose of type hinting and how you can use it in your Python code. Furthermore, we saw how duck typing compares to type hinting through the use of Protocols. Type hinting is useful to make your code more readable and to give you more certainty about your code.