Slide 1

Slide 1 text

Python static typing with MyPy @Kartones Diego Muñoz

Slide 2

Slide 2 text

01. Intro 02. Type Hints 03. MyPy 04. Advanced Topics Agenda Agenda

Slide 3

Slide 3 text

Intro

Slide 4

Slide 4 text

Weak Typing vs Strong Typing Static Typing vs Dynamic Typing Intro

Slide 5

Slide 5 text

Strong typing helps scale up codebases Intro

Slide 6

Slide 6 text

Dynamic typing speeds up development cycles Intro

Slide 7

Slide 7 text

But requires type-checking logic, decorators, validators... Intro

Slide 8

Slide 8 text

Type hinting + type checking bring that “safety net” back Intro

Slide 9

Slide 9 text

Type Hints

Slide 10

Slide 10 text

Python 3.5 - Function/Method annotations - Method argument annotations Type Hints Python 3.6 - Variable annotations Python 2.7 - All of them * def my_method(an_integer: int = 0) -> int: the_result: int = an_integer + 1 return the_result

Slide 11

Slide 11 text

3 Modes - Comment annotations - Type hints - Stub files Type Hints

Slide 12

Slide 12 text

Comment Annotations - For Python 2 Type Hints from typing import Union def my_method(an_integer, a_float): # type: (int, float) -> Union[None, float] if a_float == 0: return None the_result = an_integer / a_float # type: float return the_result

Slide 13

Slide 13 text

Type Hints - For Python 3 Type Hints from typing import Union def my_method(an_integer: int, a_float: float) -> Union[None, float]: if a_float == 0: return None the_result: float = an_integer / a_float return the_result

Slide 14

Slide 14 text

Stub files - .pyi files - Code you cannot annotate directly (e.g. external library) - stubgen Type Hints from typing import Any, List from django.views.generic import View class MyLibView(object): http_method_names: List def __init__(self, **kwargs: Any) -> None: ... def as_view(self, cls: View, **initkwargs: Any) -> View: ... def sum_values(self, first: int, second: int) -> int: ...

Slide 15

Slide 15 text

- Primitives Type Hints from typing import Union def my_method(an_integer: int, a_float: float) -> Union[None, float]: if a_float == 0: return None the_result: float = an_integer / a_float return the_result

Slide 16

Slide 16 text

- Unknown type Type Hints from typing import Any class MyDjangoView(View): def post(self, request: WSGIRequest, **kwargs: Any) -> JsonResponse: pass

Slide 17

Slide 17 text

- Lists and Tuples Type Hints from typing import List, Tuple def numeric_tuple_to_list(source_tuple: Tuple) -> List: return [item for item in source_tuple] def numeric_tuple_to_list(source_tuple: Tuple[int]) -> List[int]: return [item for item in source_tuple]

Slide 18

Slide 18 text

- Dictionaries Type Hints from typing import Dict def my_method(input_data: Dict) -> None: for key, value in input_data.items(): print(f"{key}:{value}") def my_method_2(input_data: Dict[str, int]) -> None: for key, value in input_data.items(): print(f"{key}:{value}")

Slide 19

Slide 19 text

- Multiple types Type Hints from typing import Union def my_method(an_integer: int, a_float: float) -> Union[None, float]: if a_float == 0: return None the_result: float = an_integer / a_float return the_result

Slide 20

Slide 20 text

- “Nullable type” (type or None) Type Hints from typing import Optional def my_method(an_integer: int, a_float: float) -> Optional[float]: if a_float == 0: return None the_result: float = an_integer / a_float return the_result

Slide 21

Slide 21 text

Annotations not enforced by Python Left for type checkers to use (gradual typing) Type Hints

Slide 22

Slide 22 text

MyPy

Slide 23

Slide 23 text

Static type checker tool MyPy

Slide 24

Slide 24 text

How to run - From command line MyPy mypy myproj myproj/models.py:46: error: Function is missing a type annotation for one or more arguments myproj/models.py:54: error: Unsupported operand types for >= ("int" and "str") myproj/tests/models_test.py:253: error: Argument 2 to "my_method" of "MyClass" has incompatible type "int"; expected "str"

Slide 25

Slide 25 text

How to run - As a “linter” test MyPy import subprocess import sys def test_mypy_compliance() -> None: mypy_binary = subprocess.check_output("which mypy", shell=True, stderr=sys.stderr) .decode("ascii") .replace("\n", "") result = subprocess.call("{} apu".format(mypy_binary), shell=True, stdout=sys.stdout, stderr=sys.stderr) assert result == 0

Slide 26

Slide 26 text

How to run - As an IDE plugin MyPy

Slide 27

Slide 27 text

Advanced Topics

Slide 28

Slide 28 text

Python 3 - Inform that a variable is coerced to a certain type Advanced Topics from typing import cast # let’s imagine my_input comes from an external module print(5 + cast(int, my_input))

Slide 29

Slide 29 text

Python 3 - Supports Inheritance Advanced Topics class Vehicle: pass class Car(Vehicle): pass def start_engine(vehicle: Vehicle) -> None: print(vehicle.__class__) mycar = Car() start_engine(mycar)

Slide 30

Slide 30 text

Python 3 - Types can also be specified Advanced Topics from typing import Type def validate_type(a_vehicle_class: Type) -> bool: return a_vehicle_class in [Car, Truck]

Slide 31

Slide 31 text

Python 3 - Iterators Advanced Topics from typing import Iterator def find_items() -> Iterator[MyClass]: # ... yield item

Slide 32

Slide 32 text

Python 3 - Function/Method handlers Advanced Topics from typing import Callable def my_method1(func: Callable, value: int) -> int: return func(value) def my_method2(func: Callable[[int], int], value: int) -> int: return func(value)

Slide 33

Slide 33 text

Python 3 - Type aliases Advanced Topics from typing import List Vector = List[float]

Slide 34

Slide 34 text

Python 3 - Forward references are problematic - Solution: - Python < 3.7: Quotes - Python >= 3.7: PEP 563: Postponed Evaluation of Annotations Advanced Topics class ClassA: def a_method(self, target: "ClassB") -> "ClassB": target.b_method() return target class ClassB: def b_method(self) -> None: pass

Slide 35

Slide 35 text

Python 3 - Import cycles are problematic - Solution: Type checking flag + Quotes Advanced Topics from typing import TYPE_CHECKING if TYPE_CHECKING: from b import ClassB def some_method(target: "ClassB") -> None: target.b_method() from a import some_method class ClassB: def b_method(self) ->None: pass def a_method(self) -> None: some_method(self)

Slide 36

Slide 36 text

Dropbox’s PyAnnotate: Automatically annotate your code Advanced Topics

Slide 37

Slide 37 text

Needs changes to your tests → use Pytest-annotate Advanced Topics

Slide 38

Slide 38 text

Python 2-style comment annotations :( Advanced Topics

Slide 39

Slide 39 text

Instagram’s MonkeyType: Automatically annotate your code Advanced Topics

Slide 40

Slide 40 text

Custom Python profiler → no need to change code Advanced Topics

Slide 41

Slide 41 text

Python 3-style type hints :) Advanced Topics

Slide 42

Slide 42 text

Type hints: https://www.python.org/dev/peps/pep-0483/ https://www.python.org/dev/peps/pep-0484/ MyPy: http://mypy-lang.org/ PyAnnotate: https://github.com/dropbox/pyannotate https://github.com/kensho-technologies/pytest-annotate MonkeyType: https://github.com/Instagram/MonkeyType Advanced Topics

Slide 43

Slide 43 text

THE END Thanks! @Kartones Slides: https://speakerdeck.com/ticketeaeng