Robust Python with Type Hints. Type hinting is an optional feature in... | by Amine Kaabachi | Towards Data Science

As for all dynamic languages, there are few challenges to write robust and high-quality code. To cite the main ones:
As there are no indications on Types of the objects that are passed around, programs usually end up with runtime issues. The developers gain time by writing typeless code but then lose it to write exhaustive tests to ensure that runtime issues are eliminated.
The development experience is usually limited. The fact that types are unknown forces IDEs (like PyCharm) to infer them, which generally causes performance issues, gives inaccurate autocompletes / recommendations and limits the debugging features.
As the code is typeless, the difficulty of understanding your code grows according to your project size. People usually need to run the code and debug to be able to comprehend the logic fully.
A possible solution for these challenges is the use of Python type hints. Here how they can be useful:
Using Type Checkers as they help catch errors before execution.
They make your code easy to understand and help you write documentation faster as docstrings’ generators use them when applying the templates.
Improve your development workflow by helping IDEs and linters statically analyze your code to detect errors and provide recommendations for you faster.
Build and maintain a clean architecture. Writing type hints forces you to think about your program’s types and encourages them to DRY more often.
Note that type hints are usually useful for large projects. They can become an overhead for scripting or small apps. It would be best if you typically put them in projects where you are going to write unit-tests.
Type hints in Python are optional. You can annotate your functions and hint as many things as you want; your scripts will still run regardless of the presence of annotations because Python itself doesn’t use them.
You can hint your objects using these three methods:
Special comments.
Function annotations.
Stub files for modules.
For demonstration, we will be using mypy a static type checker for Python 3 and Python 2.7. It combines the expressive power and convenience of Python with a powerful type system and compile-time type checking.
The first method to hint your object is during variable assignments. When your variable is of a simple type (e.g., int, string), checks can directly infer its type. However, when the type is complex (e.g., list of dict, list of class), detecting type becomes challenging.
Special #type: type type comments can be added during the assignment to hint types:
If we run the code with a Python Interpreter. We would get the following result:
▶ python specialcomment.pyGoogle
No exception or error was thrown. The # type comments have been ignored and treated as plain comments which have no additional semantic meaning.
Let’s try now to pass to mypy and check the results:
▶ mypy specialcomment.py specialcomment.py:11: error: Incompatible types in assignment (expression has type "str", target has type "int")Found 1 error in 1 file (checked 1 source file)
You can see that it complains that we are assigning a string in a place where int type is expected. That can help you a lot in real-life situations to detect possible bugs in your logic.
Function annotations specify functions argument types and return value types. Let’s take a look at an example:
You add the type to the arguments by appending a : type in front each of them. The return value however, is specified with -> type notation before the ending function colon.
All annotations are stored in the __annotations__ attribute dict and can be accessed, as shown in the results below:
▶ (python) pi_approx.__annotations__{'num_iterations': <class 'int'>, 'return': <class 'float'>}
Imagine you would have the following code that uses pi_approx:
Python would throw an error if we execute this code:
▶ python3 pi_approx_error.pyTraceback (most recent call last):  File "pi_approx_error.py", line 14, in <module>    pi = pi_approx(iterations)  File "pi_approx_error.py", line 5, in pi_approx    for i in range(num_iterations):TypeError: 'float' object cannot be interpreted as an integer
Let’s see if mypy can catch our bug before runtime:
▶ mypy pi_approx_error.pypi_approx_error.py.py:14: error: Argument 1 to "pi_approx" has incompatible type "float"; expected "int"
As you can see, it becomes super beneficial! It can even slide down to detect errors in using standard python functions and libs, which is the power behind it.
Stub files can be used to apply type hints without altering an existing codebase. It is usually useful for cases where you want to reduce the risk of regression (e.g., in legacy codebases) or If you’re going to improve the interfaces of an existing module that you are not directly responsible for.
Stub files have .pyiextention. They are annotated interfaces of the modules you are planning to use. Let’s go back to our PI example:
We can create a stub file pi_approx.pyi, specify the type hints. Function bodies cannot be removed entirely. By convention, we replace them with ...We can do the same with default arguments too.
Automation is key to be sure you are writing good quality code. I usually use nox to automate my development workflow. Here is my noxfile:
Executing nox simply runs tests, lints and typechecks the codebase with different python versions. You can execute the typecheck session only by running nox -s typecheck .
Should you use type hints in your code? As we said in the introduction, it usually depends on your project size and goal.
What about the existing codebase and the migration cost? Python supports the concept of gradual typing. It means that you can gradually introduce types into your code. The static type checker will ignore code without type hints. You can also use stabs for old code so that you minimize regression risk.
I hope this article makes you familiar with the basic concepts of Type Hints in Python. Please have a look at the references below to dive deeper into type hints and mypy advanced usage.
For more tips on writing high-quality python code refer to the following article: