Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Сase Study: Type Hints

Сase Study: Type Hints

Михаил Юматов (ЦИАН) @ Moscow Python Conf 2017
"У нас большой проект, много кода и разработчиков. Целый пласт возникающих багов связан с отсутствием проверки типов. Попробовали mypy и type hints, получили профит (больше документации, меньше багов), теперь покрываем type hint'ами все новые микросервисы в компании. Расскажу на примерах, что можно получить, а чего ждать не стоит".

Moscow Python Meetup

October 20, 2017
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. М И Х А И Л Ю М А Т

    О В T Y P E H I N T S
  2. • Руководитель разработки в ЦИАН • В ЦИАНе с 2015

    • 10 лет разработки на Python О С Е Б Е
  3. • Ошибки в коде • AttributeError: NoneType object has no

    attribute … • TypeError: must be str, not int • Документация • Лишние тесты • Метаклассы и автокомплит П Р О Б Л Е М Ы
  4. def greeting(name: str) -> str: return 'Hello ' + name

    class Foo: bar: int def __init__(self, bar: int) -> None: self.bar = bar P E P - 4 8 4 : T Y P E H I N T S
  5. def greeting(name: str) -> str: return 'Hello ' + name

    print(greeting(777)) print(greeting(None)) $ mypy test.py --strict error: Argument 1 to "greeting" has incompatible type "int"; expected "str” error: Argument 1 to "greeting" has incompatible type "None"; expected "str" M Y P Y
  6. • https://github.com/python/typeshed • stub files (*.pyi) class Enum(BaseEnum): @classmethod def

    has_lcname(cls, name: bytes) -> bool: ... @classmethod def get_by_lcname(cls, ccname: bytes) -> int: ... lcname = ... # type: bytes Н Е Т Т И П О В В Б И Б Л И О Т Е К А Х
  7. setup.py: setup( name='cian-enum', … data_files=[ ('stubs/cian_enum', ['cian_enum/__init__.pyi']), ('stubs/cian_enum', ['cian_enum/base.pyi']), ],

    ) bin/mypy: prefix=$(python -c 'import sys; print(sys.prefix)') MYPYPATH=$prefix/stubs mypy "$@" Н Е Т Т И П О В В Б И Б Л И О Т Е К А Х
  8. class Offer(Entity): id = attrs.Integer() geo = attrs.ValueObject(Geo) offer =

    Offer(...) offer.geo.region $ mypy test.py --strict error: ”ValueObject" has no attribute ”region" М Е Т А К Л А С С Ы
  9. class Offer(Entity): id = attrs.Integer() geo = attrs.ValueObject(Geo) offer =

    Offer(...) offer.geo.region entity/base.pyi from typing import Type, TypeVar T = TypeVar('T') def ValueObject(klass: Type[T], **kwargs) -> T: ... М Е Т А К Л А С С Ы : П О П Ы Т К А 1 , S T U B S
  10. • Это хак • Он работает в mypy • Но

    не работает в PyCharm М Е Т А К Л А С С Ы : П О П Ы Т К А 1 , S T U B S
  11. Было (Python): class AgentPhone(Entity): id = attrs.Integer(min_value=0, help='ID телефона') country_code

    = attrs.String(help='Код страны') code = attrs.String(help='Код оператора') number = attrs.String(help='Номер') confirmed = attrs.Boolean(help='Подтвержден') visible = attrs.Boolean(help='Опубликован') М Е Т А К Л А С С Ы : П О П Ы Т К А 2 , C O D E G E N
  12. Стало (JSON Schema): AgentPhone: type: object description: Телефон агента required:

    [id] properties: id: {type: integer, minimum: 0, description: "ID телефона"} countryCode: {type: string, description: "Код страны"} code: {type: string, description: "Код оператора"} number: {type: string, description: "Номер"} confirmed: {type: boolean, description: "Подтвержден"} visible: {type: boolean, description: "Опубликован"} М Е Т А К Л А С С Ы : П О П Ы Т К А 2 , C O D E G E N
  13. Стало (JSON Schema): class AgentPhone: id: int country_code: Optional[str] code:

    Optional[str] number: Optional[str] confirmed: Optional[bool] visible: Optional[bool] _meta = ... def __init__(...): ... М Е Т А К Л А С С Ы : П О П Ы Т К А 2 , C O D E G E N
  14. • Все легально • Работает в mypy • Работает в

    PyCharm • Работает автокомплит М Е Т А К Л А С С Ы : П О П Ы Т К А 2 , C O D E G E N