Типизация в питоне

Данилов Константин, Mirantis

http://koder-ua.blogspot.com/

https://github.com/koder-ua/

Настоящие пацаны на английском

Dec 2016 BayPiggies Talk at LinkedIn: Introducing Type Annotations for Python - https://www.youtube.com/watch?v=ZP_QV4ccFHQ

Проблемы

    def do_something(data, size, operations, dim=12):
        .....
* Подавляющее большинство кода типизировано (pypy/psyco)
* Особенно библиотеки

Способы решение

* докстринги - не фонтан
* Type inference - RPython, pypy, nuitka, etc
* typechecker - не совсем то

python 3000

* function annotation
* cython, pychecker, etc
    def f(x: 3, y: int) -> "something"
        pass

mypy

* mypy-lang.org
* typing модуль в 3.5+
* типизация переменных в 3.6

Как работает - базовые типы

    $ pip install mypy-lang
    $ mypy PYTHON_FILES
    ALL_FILES=$(shell find wally/ -type f -name '*.py')
    STUBS="stubs:.env/lib/python3.5/site-packages"
    ACTIVATE=cd ~/workspace/wally; source .env/bin/activate

    mypy:
            bash -c "${ACTIVATE}; MYPYPATH=${STUBS} python3 -m mypy -s ${ALL_FILES}"

mypy in action

def f(x, y):
    r = x + y
    return r

def f1(x: int, y: int) -> int:
    # mypy и сам это умеет
    r = x + y  # type: int
    return r

f(1, 2)
f(1, "2")
f1(1, "2")  # code.py:98: error: Argument 2 to "f1" has
            # incompatible type "str"; expected "int"

mypy in action

    d = {1: 2, 3: 4}
    for key, val in d:
        pass

    # code.py:101: error: 'builtins.int*' object is not iterable

Как работает - контейнеры

from typing import List, Dict, Tuple, Optional, Callable

T = List[int]
Y = Dict[str, T]
M = Optional[str]
C = Callable[[str, str], int]
C1 = Callable[[], int]

Как работает - более сложные случаи

from typing import TypeOf

T1 = TypeVar('T1', Callable)
def extract_func(data: Tuple[int, T1]) -> T1:
    return data[1]

#---------------------------------------------------

MyType = TypeVar('MyType')
def factory(c: TypeOf[MyType], *args, **kwargs) -> MyType:
    return c(*args, **kwargs)

class D:
    pass

x = factory(D)  # type: D

Типизация в комментариях

def f1(x, y):
    # type: (int, int) -> int
    r = x + y  # type: int
    return r

Типизация в pyi файлах

* Именно там лежат стабы для stdlib 
* Называете файл как модуль, но c расширением pyi
* Поместить в папку, где его найдет mypy
    def f1(x: int, y: int) -> int :...

Forward reference

    class X:
        def copy(self) -> 'X':
            ...

Что меняется

* Циклические зависимости - интерфейсы
* Тут вам не Go. Наследовать интерфейся нужно явно

Проблемы c Optional

* --strict-optional - cool!

Проблемы - поддержка

* pycharm  - 3 from 5

Проблемы - баги

* 534 открытых тикета в трекере. БОльшая часть баги.
    # fails in pycharm
    def f() -> Iterable[int]:
        yield 1

Проблемы - баги. На коротких примерах не воспроизводится

    class Storage():
        def append(self, header: List[str], value: numpy.array, *path: str) -> None:
            ...

    class ResultStorage(IResultStorage):
        storage = None  # type: Storage
        ....
        def append_sensor(self, data: numpy.array, node_id: str, metrics_fqn: str) -> None:
            ...
            return self.storage.append([node_id, metrics_fqn, "unknown"], data, path)

    # wally/hlstorage.py: note: In member "append_sensor" of class "ResultStorage":
    # wally/hlstorage.py:165: error: No return value expected

Проблемы

* Документация
* Сообщество

Сторонние библиотеки

* Тут картинка с Траволтой
* Для FP библиотек особенно сложно все

Kовариантность и контравариантность

    class X: pass
    class Y(X): pass

    def f1(data: List[X]) -> None:
        pass

    data = []  # type: List[Y]

    f1(data)
    # FAIL - Argument 1 to "f1" has incompatible type List[Y]; expected List[X]

    f1([Y(), Y()])    # OK

Нехватка функциональности

* Копирование типов
    def mydec(func: Callable[????]) -> Callable[????]:
        def closure(p1: int, *args, **kwargs):
            print(p1)
            return func(*args, **kwargs)
        return closure 

    X = TypeVar('X')
    def add1(x: X) -> typeof(X + int):
        return x + 1

XXX

    class X:
        def __init__(self):
            pass

    # code.py: note: In member "__init__" of class "X":
    # code.py:66: error: Function is missing a type annotation

    class X:
        def __init__(self) -> None:
            pass
    # OK

XXX

    class X:
        def __add__(self, o: 'X') -> 'X':
            pass

    # code.py: note: In member "__init__" of class "X":
    # code.py:66: error: Function is missing a type annotation

    class X:
        def __init__(self) -> None:
            pass
    # OK

Появляются плохо читаемы описания типов

* Callable[[A, Dict[B, T], List[M]], C]  == (A, {B:T}, [M]) -> C

Без cast пока никуда

    # OK
    auth_url = cast(str, openrc['os_auth_url'])

    # NOT OK
    RequestMethod = Callable[[str], Callable]
    PUT = cast(RequestMethod, partial(make_call, 'put'))  # type: RequestMethod

Держите себя в руках

Итоги

Вопросы и помидоры

/