Modernize Your Sinful Python Code with Beautiful Type Hints | by Eirik Berge | Towards Data Science

Setting the StageUnderstanding Dynamic Types and Static TypesPython Type Hints for VariablesChecking Type Hints with MypyPython Type Hints for FunctionsHow to Add Lists as Type HintsFive Awesome Type Hints to Power Up Your CodeWhy All The Cool Kids Use Python 3.9+Wrapping Up and Further Resources
Setting the Stage
Understanding Dynamic Types and Static Types
Python Type Hints for Variables
Checking Type Hints with Mypy
Python Type Hints for Functions
How to Add Lists as Type Hints
Five Awesome Type Hints to Power Up Your Code
Why All The Cool Kids Use Python 3.9+
Wrapping Up and Further Resources
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.
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.”
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, mypy is easy to use!
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 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
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 class 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!
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 valueFound 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.
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 🐍
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.
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__.
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.
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.
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.
Before Python 3.9, you needed to import List and Dict from the typing module if you wanted to provide those type hints.
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 😍
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.
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.
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 ✋