The world’s leading publication for data science, AI, and ML professionals.

Modernize Your Sinful Python Code with Beautiful Type Hints

Become an Apex Predator Pythonista With Expert Code Quality Skills

Overview of Your Journey

  1. Setting the Stage
  2. Understanding Dynamic Types and Static Types
  3. Python Type Hints for Variables
  4. Checking Type Hints with Mypy
  5. Python Type Hints for Functions
  6. How to Add Lists as Type Hints
  7. Five Awesome Type Hints to Power Up Your Code
  8. Why All The Cool Kids Use Python 3.9+
  9. Wrapping Up and Further Resources

Setting the Stage

In the 2020 Developer Survey by Stack Overflow, Python took the top spot in the category "programming languages developers want to learn". In spite of this, Python has drawbacks that annoy supporters and feed critics. One of the drawbacks that are often mentioned is the following:

Python has dynamic types. This increases debugging time and makes the behaviour of variables and functions unpredictable.

Although dynamic types are convenient for rapid prototyping, they can become an annoyance in larger projects. This is where type hinting comes into play: It is possible since the release of Python 3.5 to emulate static types in Python by writing type hints! ❤️

Another advantage of writing type hints is that they improve Code Quality. When I say code quality, most people think of descriptive variable names and well-written docstrings. Writing type hints is a modern addition for improving your code quality in Python.

In this article, I will show YOU how to use type hints to supercharge your Python code!

Goals: I will teach you the basics of type hinting in Python. After the basics, I will show you five more awesome type hints to power up your Python code. Finally, I will explain how type hints recently became less of a hassle with the introduction of Python 3.9.

Prerequisites: You should be familiar with the basics of Python. Make sure that the Python version you are running is higher than Python 3.5. No prerequisites on dynamic types, static types, or type hints are necessary.


Understanding Dynamic Types and Static Types

First of all, I should explain the terms dynamic types and static types. In Python, you can change the data type of a variable as many times as you want throughout the program:

Notice from the code snippet above that:

  • I don’t specify the data type of the variable my_variable when defining it.
  • I can change the data type of the variable my_variable throughout the Python program.

In short, Python has dynamic types.

In some programming languages (for example Java) you need to specify the data type of a variable at creation. Moreover, the data type of a variable can not change later in the program. We say that such programming languages have static types.

Programming in a language with static types might seem like a burden. Yet, debugging a programming language with static types is often less cumbersome. With static types, you can find errors earlier and fix them with minimal effort.

To put it simply:

  • This is your face when debugging with dynamic types 😢
  • This is your face when debugging with static types 😎

I will show you how to emulate static types in Python with type hints!

Fun fact: Dynamic typing is sometimes called duck typing. The terminology comes from the well-known saying: "If it walks like a duck and it quacks like a duck, then it must be a duck."


Python Type Hints for Variables

I will begin with the following application of type hints:

Type hints allow you to specify data types for variables.

Since code snippets speak louder than words, take a look at the following example:

I have, with the power of type hints, specified that my_variable should be an integer. That was not too bad! 😃

You can use standard data types such as float, bool, and string **** for type hints as well. The following code snippet records some useful features of a car:

If you try to run the above code snippet in a script, then Python has no complaints. Everything seems peachy! However, this does not tell you that Python checks that the assigned types are correct. In fact, try to run the following code:

What!? Python does not give any errors with the wrong type hint 😧

This is because Python, although it understands the syntax of type hints, does not care about it at all.

You will need to use an external type checker to check that the type hints match the provided variables. An external type checker is often an external Python module. I will show you how to use the Mypy module ** for external type checking. Don’t worry, myp**y is easy to use!


Checking Type Hints with Mypy

The one-line description of mypy is as follows:

If you sprinkle your code with type hints, mypy can type check your code and find common bugs.

To install mypy, use PIP (Package Installer for Python) with the command:

pip install mypy

If this did not work, then check out the documentation for installation help. Create a file called simple_type_hint.py **** with the following line of code:

Open up your terminal and navigate to the folder where you created the file simple_type_hint.py. Then run the command:

python -m mypy simple_type_hint.py

This should display the message:

Success: no issues found in 1 source file

It seems like mypy is okay with everything written so far. I now want to intentionally break the type hint to see what happens 😈

Add the following line of code to simple_type_hint.py:

In the above code snippet, I have specified with a type hint that my_variable should be an integer. Even so, I later break the type hint by assigning a string to my_variable.

If you run the file simple_type_hint.py **** as a Python program, then there is no error displayed as usual. However, try to use mypy with the command:

python -m mypy simple_type_hint.py

This should display something like the following message:

simple_type_hint.py:5: error: Incompatible types in assignment (expression has type "str", variable has type "int")
Found 1 error in 1 file (checked 1 source file)

Unlike Python, mypy detects what is wrong. The error message above specifies both the line number (in my case 5) and the precise problem that occurs.

IT WORKS!!!

It feels great to have a working example, no matter how simple 🔥

Fun Fact: At mypy’s GitHub Page, there is an issue from 2019 (Issue #6740) suggesting that mypy should stop referring to itself as alpha software. The term alpha software refers to software in the pre-release/early version stage. The creator of Python, Guido van Rossum, steps into the discussion to set things straight:

"We’re being very self-deprecating here. I would not hesitate calling mypy production quality, i.e. beyond beta." – Guide van Rossum

Python Type Hints for Functions

Most Python programs have plenty of functions in them. I will now show you how to work with Type Hints for functions.

In an empty file with the name simple_function.py **** I wrote the following simple function:

The function both_divisible_by_two accepts two integers as inputs. It then checks whether both of them are divisible by two. Pretty straightforward, right? Now I add type hints to the function both_divisible_by_two:

Notice that only the first line of both_divisible_by_two has changed:

  • I wrote a: int and b: int to indicate that the inputs a and b of both_divisible_by_two should be integers.
  • To indicate that the output of both_divisible_by_two is a Boolean value I use the syntax -> bool.

If you now run the usual command

python -m mypy simple_funciton.py

then you should get the pleasant output

Success: no issues found in 1 source file.

Look great! It is always a good sanity check to see whether mypy reacts if you intentionally make a wrong type hint. To try this out, I alter both_divisible_by_two **** as follows:

If you again run the command

python -m mypy simple_funciton.py

then you should roughly get the error message

simple_function.py:4: error: Incompatible return value type (got "bool", expected "str")
Found 1 error in 1 file (checked 1 source file)

It works! 😃

Hint: If you change the type hint for the output to int ** in both_divisible_by_two then mypy would give no errors. Do you understand why? It has to due with a subtle implementation detail in Python: The Boolean class `bool** is a subclass of the integer clas**s**int` under the identification

0 <-> False ** and** 1 <-> True.

Hence Boolean values "are" integers in Python. Keep this detail in mind when you are working with type hints!


How to Add Lists as Type Hints

We’ve seen how to add basic type hints to a function. How would you type hint that a variable should be a list? Unfortunately, the obvious approach (to write list) is not valid in Python version 3.8 and lower 😒

You actually need to reach into the built-in module typing ** to grab the type hint for a list. Let me open a new file called `list_type_hints.py`** and start the file with the line:

Notice that List is capitalized. Create a function called append_element **** that appends a value to a given list:

So far, so good. The function append_element would crash if passed a dictionary for the data parameter. To ensure this, you can add type hints as follows:

If you run mypy on the file list_type_hints.py **** with the command

python -m mypy list_type_hints.py

then mypy reports the following:

list_type_hints.py:5: error: "append" of "list" does not return a value
Found 1 error in 1 file (checked 1 source file)

What is this? It seems like mypy has found an error in our code 😱

What is the problem? Scrutinize mypy’s response above before continuing.

Solution: The list method append **** modifies lists in place but returns None. Hence the statement

return data.append(value)

will always return the same as

return None

To fix the code, you should make the following simple edit:

It is awesome to find the problem right away! Here type hints found an error that the usual Python interpreter would not alert you about 😎

The type hints have the added benefit that they document the code better. It is now clear in append_element that data should be a list. I could also have used a more descriptive variable name than data. Descriptive variable names and type hints can both improve the quality of the code.

So far, the elements in the list passed into append_element can be of any type. It can for instance be a list of integers, a list of strings, or a list consisting of both integers and strings. If I only want to accept a list of integers, then I can change the code as follows:

Notice that I have exchanged the type hint List with the more specific type hint List[int]. Now the list input and list output can only contain integers!

Hint: In the typing module, there are other container data types like dictionaries Dict and tuples Tuple. I invite you to check these out whenever you need them.

Five Awesome Type Hints to Power Up Your Code

You are now familiar with the basics of type hinting in Python. It is hence time to learn some more advanced features. I will show you five awesome type hints that will supercharge your Python code 🐍


Iterable

It is sometimes too restrictive to require that a variable or function argument should be a List. In fact, as long as iteration is possible, the type is often acceptable. For this situation, you can use the Iterable type hint. Consider the following print_everything function:

Calling the function print_everything on anything that supports iteration (for example lists, tuples, strings) is now possible.

Hint: In Python, a function that does not have an explicit return statement returns by default None. This is the reason why I have added the -> None ** type hint for the output of the function** print_everything.


Callable

Python is a programming language that supports higher-order functions. A consequence of this is that you can pass functions as arguments to other functions. Consider the following wrapper_print function:

The attribute __name__ gives the name of the function. Notice that both the input and the output of wrapper_print ** are functions**. I can specify this as a type hint by using the Callable type hint:

When trying to understand what a callable is, you frequently run into the saying "A callable is anything that can be called." 😑

If you are unsure, then Python has the built-in function callable to the rescue. The function callable checks whether something is callable or not. In everyday coding, it’s for the most part functions you should have in mind when specifying Callable as a type hint.

Hint: Rest assured that functions (both stand-alone functions and class methods) are callable. Conversely, rest assured that none of the data types int, float, bool, str, None, list, dict, tuple, or set are callable. An interesting example of a callable is an instance of a class that has the dunder method __call__.


Union

Sometimes, requiring that a variable or function argument could only be of a single type is too restrictive. A common example is a variable that could be both an integer and a floating-point number. Consider the type hint

It is conceivable that hourly_wage **** could also be an integer. You can use the Union type hint to specify many type hints:

Inside Union, specify each appropriate type for the variable separated by commas. Notice that there is a trade-off situation here: By adding many type hints with Union you get more flexibility. However, as your type hints get more flexible they also become less useful.

Hint: In Python 3.10, there is a brand new syntax that is available: Instead of Union you can use the operator | to separate different type hints. Hence we can exchange Union[int, float] **** with int | float in Python 3.10+.

Most companies as of today are not yet using Python 3.10. Hence I would recommend getting familiar with the classical syntax by using Union.


Optional

Commonly, certain arguments in functions are by default set to None. Consider, as an example, the following lowercase_playlist function:

In lowercase_playlist the songs ** parameter is set to None by default. If no value is provided for songs, then `songs` is set to a list containing the single song "Everything is awesome!".** Finally, we return the lowercase versions of the songs by using a list comprehension.

You could write Union[List, None] to provide an input type hint for lowercase_playlist. However, this is so common that there is the special shorthand syntax Optional[List]:

Hint: You should NOT use mutable objects (such as a list) for default arguments in functions. Doing this often leads to annoying bugs. I recommend Chapter 8 of Al Sweigart’s new book Beyond the Basic Stuff with Python for more on this problem.


Any

Finally, you can provide a catch-all type hint with Any. The Python documentation simply states:

Any – Special type indicating an unconstrained type. Every type is compatible with Any. Any is compatible with every type.

Why do you need Any when you can omit type hints? 😕

The main use for Any is, in my opinion, to show to other developers that the lack of a restrictive type hint is a conscious choice. If you simply did not specify a type hint, then other developers could start to wonder:

Did Mark forget to add a type hint here? Is he being sloppy? Hm…

Be attentive to your fellow developers and use the Any type hint whenever appropriate.


Why All the Cool Kids Use Python 3.9+

Before Python 3.9, you needed to import List and Dict from the typing module if you wanted to provide those type hints.

NO MORE!

In Python 3.9 there is finally native support for lists and dictionary type hints. That means that you DON’T need to use the typing module to import List and Dict for type hinting. In Python 3.9+ you can use the lowercase list and dict for type hints:

Hint: Before Python 3.9, you had to capitalize List and Dict when importing them from the typing module. This is to avoid overriding the built-in list() and dict() constructor functions. In Python 3.9+ you can safely use the lowercase versions list and dict for type hints without overwriting anything.

Ahh! The only thing better than fewer imports is syntax consistency 😍


Wrapping Up & Further Resources

In this article, I have gone through type hints in Python. Type hints are a modern way to sneak in some of that sweet static typing into your Python code.

Further Resources

There is no better place for the knowledge-hungry Pythonista to start than PEP484. Moreover, the cool people at mypy have made an awesome cheat sheet.

Hint: You can incorporate type hints bit by bit in your projects. There is no problem having part of the code statically typed, while the rest is dynamically typed.

More Similar Content?

If you like my writing, then check out my other blog posts such as Learn Some Front-End Web Development as a Data Scientist and 5 Awesome NumPy Functions That Can Save You in a Pinch. If you are interested in data science, programming, or anything in between, feel free to add me on LinkedIn and say hi ✋


Related Articles