All.type hints.how to guide
Type Hints#
Type Hints#
Why We Use Type Hints#
- We use Python 3 type hints to:
- Improve documentation
- Allow mypy to perform static checking of the code, looking for bugs
- Enforce the type checks at run-time, through automatic assertions (not implemented yet)
What to Annotate with Type Hints#
- We expect all new library code (i.e., that is not in a notebook) to have type annotations
- We annotate the function signature
- We don't annotate the variables inside a function unless
mypyreports that it can't infer the type - We strive to get no errors / warnings from Linter, including
mypy
Conventions#
Empty Return#
- Return
-> Noneif your function doesn't return- Pros:
mypychecks functions only when there is at least an annotation: so using-> Noneenables mypy to do type checking- It reminds us that we need to use type hints
- Cons:
Noneis the default value and so it might seem redundant
- Pros:
Invoke Tasks#
-
For some reason
invokedoes not like type hints, so we- Omit type hints for
invoketasks, i.e. functions with the@taskdecorator - Put
# type: ignoreso thatmypydoes not complain
- Omit type hints for
-
Example:
python @task def run_qa_tests( # type: ignore ctx, stage="dev", version="", ):
Annotation for kwargs#
- We use
kwargs: Anyand notkwargs: Dict[str, Any] *always binds to aTuple, and**always binds to aDict[str, Any]. Because of this restriction, type hints only need you to define the types of the contained arguments. The type checker automatically adds theTuple[_, ...]andDict[str, _]container types.- Reference article
Any#
Anytype hint = no type hint- We try to avoid it everywhere when possible
np.array and np.ndarray#
- If you get something like the following lint:
dataflow/core/nodes/sklearn_models.py:537:[amp_mypy] error: Function "numpy.core.multiarray.array" is not valid as a type [valid-type] - Then the problem is probably that a parameter that the lint is related to has
been typed as
np.arraywhile it should be typed asnp.ndarray:python `x_vals: np.array` -> `x_vals: np.ndarray`
Handling the Annoying Incompatible types in assignment#
mypyassigns a single type to each variable for its entire scope- The problem is in common idioms where we use the same variable to store different representations of the same data ```python output : str = ... output = output.split("\n") ...
Process output.#
...
output = "\n".join(output)
- Unfortunately the proper solution is to use different variablespython
output : str = ...
output_as_array = output.split("\n")
...
Process output.#
...
output = "\n".join(output_as_array)
- Another case could be:python
from typing import Optional
def test_func(arg: bool):
...
var: Optional[bool] = ...
dbg.dassert_is_not(var, None)
test_func(arg=var)
- Sometimes `mypy` doesn't pick up the `None` check, and warns that the function
expects a `bool` rather than an `Optional[bool]`. In that case, the solution
is to explicitly use `typing.cast` on the argument when passing it in, note
that `typing.cast` has no runtime effects and is purely for type checking.
- Here're the relevant [docs](https://mypy.readthedocs.io/en/stable/casts.html)
- So the solution would be:python
from typing import cast ...
...
test_func(arg=cast(bool, var))
```
Handling the Annoying "None" has no attribute#
- In some model classes
self._modelparameter is being assigned toNonein ctor and being set after callingset_fit_statemethod - The problem is that statically it's not possible to understand that someone
will call
set_fit_statebefore usingself._model, so when a model's method is applied: ```python self._model = self._model.fit(...)the following lint appears: ```bash dataflow/core/nodes/sklearn_models.py:155:[amp_mypy] error: "None" has no attribute "fit" - A solution is to
- Type hint when assigning the model parameter in ctor:
python self._model: Optional[sklearn.base.BaseEstimator] = None - Cast a type to the model parameter after asserting that it is not
None:python hdbg.dassert_is_not(self._model, None) self._model = cast(sklearn.base.BaseEstimator, self._model)
- Type hint when assigning the model parameter in ctor:
Disabling mypy Errors#
- If
mypyreports an error and you don't understand why, please ping one of the Python experts asking for help - If you are sure that you understand why
mypyreports and error and that you can override it, you disable thiserror - When you want to disable an error reported by
mypy:- Add a comment reporting the
mypyerror - Explain why this is not a problem
- Add
# type: ignorewith two spaces as usual for the inline comment - Example ```python
- Add a comment reporting the
mypy: Cannot find module named 'pyannotate_runtime'#
pyannotate is not always installed#
from pyannotate_runtime import collect_types # type: ignore ```
What to Do When You Don'T Know What to Do#
- Go to the
mypyofficial cheat sheet - Use
reveal_type- To find out what type
mypyinfers for an expression anywhere in your program, wrap it inreveal_type() mypywill print an error message with the type; remove it again before running the code- See
the official
mypydocumentation
- To find out what type
Library Without Types#
mypyis unhappy when a library doesn't have types- Lots of libraries are starting to add type hints now that Python 2 has been
deprecated
*.py:14: error: No library stub file for module 'sklearn.model_selection' [mypy] - You can go in
mypy.iniand add the library (following the alphabetical order) to the list - Note that you need to ensure that different copies of
mypy.iniin different sub projects are equal> vimdiff mypy.ini amp/mypy.ini or > cp mypy.ini amp/mypy.ini
Inferring Types Using Unit Tests#
- Sometimes it is possible to infer types directly from unit tests. We have used
this flow to annotate the code when we switched to Python3 and it worked fine
although there were various mistakes. We still prefer to annotate by hand
based on what the code is intended to do, rather than automatically infer it
from how the code behaves.
- Install
pyannotate> pip install pyannotate - To enable collecting type hints run
> export PYANNOTATE=True - Run
pytest, e.g., on a subset of unit tests: - Run
pytest, e.g., on a subset of unit tests likehelpers:> pytest helpers - A file
type_info.jsonis generated - Annotate the code with the inferred types:
> pyannotate -w --type-info type_info.json . --py3
- Install