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

Пишем на питоне так, чтобы все вас любили (или ненавидели)

Пишем на питоне так, чтобы все вас любили (или ненавидели)

Никита Соболев (CTO в Wemake.services) @ Moscow Python Meetup 67

"В своем докладе я хочу показать, как просто и как сложно писать простой код.

Чтобы разобраться: что сложно, а что просто; нам придется поговорить о разнице между м*&*ами и algebraic effects, SRP, о плюсах и минусах Typed DI, протоколах и приставучем IO. А еще поговорим про цвета функций, DDD, типы и клей для композиции.

Разложив все по полочкам, мы сможем написать первые несколько строк простого кода для большого проекта".

Видео: http://www.moscowpython.ru/meetup/67/python-love-or-hate/

Moscow Python Meetup
PRO

August 21, 2019
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. Никита Соболев
    github.com/sobolevn
    1

    View Slide

  2. Как писать на питоне
    так, чтобы все вас
    ненавидели любили
    !2

    View Slide

  3. Ни одной м***ы
    !3

    View Slide

  4. View Slide

  5. def recommended_books(books):
    book_names = [
    book.name for book in books
    ]
    good_books = [
    book_name for book_name in book_names
    if book_name not in ('Twilight', 'Learning Go Lang')
    ]
    recommended_books = [
    find_similar(book_name) for book_name in book_names
    ]
    return recommended_books
    !5

    View Slide

  6. def recommended_books(books):
    book_names = [
    book.name for book in books
    ]
    good_books = [
    book_name for book_name in book_names
    if book_name not in ('Twilight', 'Learning Go Lang')
    ]
    recommended_books = [
    find_similar(book_name) for book_name in book_names
    ]
    return recommended_books
    !6

    View Slide

  7. def recommended_books(books):
    book_names = [
    book.name for book in books
    ]
    good_books = [
    book_name for book_name in book_names
    if book_name not in ('Twilight', 'Learning Go Lang')
    ]
    recommended_books = [
    find_similar(book_name) for book_name in book_names
    ]
    return recommended_books
    !7

    View Slide

  8. def recommended_books(books):
    book_names = [
    book.name for book in books
    ]
    good_books = [
    book_name for book_name in book_names
    if book_name not in ('Twilight', 'Learning Go Lang')
    ]
    recommended_books = [
    find_similar(book_name) for book_name in book_names
    ]
    return recommended_books
    !8

    View Slide

  9. def recommended_books(books):
    book_names = [
    book.name for book in books
    ]
    good_books = [
    book_name for book_name in book_names
    if book_name not in ('Twilight', 'Learning Go Lang')
    ]
    recommended_books = [
    find_similar(book_name) for book_name in book_names
    ]
    return recommended_books
    !9

    View Slide

  10. Три действия =
    одна функция
    !10

    View Slide

  11. Композиция
    !11

    View Slide

  12. Три действия =
    одна функция
    !12

    View Slide

  13. View Slide

  14. Абстракция
    !14

    View Slide

  15. 3 = 1*
    * при соблюдении уровня абстракции
    !15

    View Slide

  16. Композиция и
    абстракция
    !16

    View Slide

  17. def recommended_books(books):
    book_names = [
    book.name for book in books
    ]
    good_books = [
    book_name for book_name in book_names
    if book_name not in ('Twilight', 'Learning Go Lang')
    ]
    recommended_books = [
    find_similar(book_name) for book_name in book_names
    ]
    return recommended_books
    !17

    View Slide

  18. def recommended_books(books):
    recommended_books = recommend(filter(transform(books)))
    return recommended_books
    !18

    View Slide

  19. Три разных слота
    !19

    View Slide

  20. View Slide

  21. transform = db lookup
    filter = api call
    recommend = cache lookup
    !21

    View Slide

  22. def recommended_books(books):
    book_names = book_repo.names_by_ids(books)
    good_books = filter_api.by_preferences(book_names)
    recommended_books = cache.not_shown(good_books)
    return recommended_books
    !22

    View Slide

  23. def recommended_books(books):
    book_names = book_repo.names_by_ids(books)
    good_books = filter_api.by_preferences(book_names)
    recommended_books = cache.not_shown(good_books)
    return recommended_books
    !23

    View Slide

  24. def recommended_books(books):
    recommended_books = recommend(filter(transform(books)))
    return recommended_books
    !24

    View Slide

  25. Нам срочно нужно
    переиспользовать!

    !25

    View Slide

  26. Может быть как
    параметры?

    !26

    View Slide

  27. def recommended_books(recommend, filter, transform, books):
    recommended_books = recommend(filter(transform(books)))
    return recommended_books
    !27

    View Slide

  28. Некрасиво!

    !28

    View Slide

  29. Может в два
    захода?

    !29

    View Slide

  30. def recommended_books(recommend, filter, transform):
    def inner(books):
    return recommend(filter(transform(books)))
    return inner
    !30

    View Slide

  31. Две вложенные
    функции можно
    записать понятнее!

    !31

    View Slide

  32. class RecommendedBooks(object):
    def __init__(self, book_repo, filter_api, cache):
    self._repo = book_repo
    self._api = filter_api
    self._cache = cache
    def __call__(self, books):
    book_names = self._repo.names_by_ids(books)
    good_books = self._api.by_preferences(book_names)
    recommended = self._cache.not_shown(good_books)
    return recommended
    !32

    View Slide

  33. Но мы все равно
    вынуждены таскать за
    собой параметры ВЕЗДЕ!

    !33

    View Slide

  34. Может нормальный
    класс напишем?

    !34

    View Slide

  35. View Slide

  36. Stop writing
    classes
    youtube.com/watch?v=o9pEzgHorH0
    36

    View Slide

  37. Что понять ООП и писать
    нормально, нам нужно:
    !37

    View Slide

  38. Что понять ООП и писать
    нормально, нам нужно:
    • Прочитать уйму умных книг
    !37

    View Slide

  39. Что понять ООП и писать
    нормально, нам нужно:
    • Прочитать уйму умных книг
    • Посмотреть на ужас Java и C++
    !37

    View Slide

  40. Что понять ООП и писать
    нормально, нам нужно:
    • Прочитать уйму умных книг
    • Посмотреть на ужас Java и C++
    • Понять прототипы из JS, протоколы из TypeScript
    !37

    View Slide

  41. Что понять ООП и писать
    нормально, нам нужно:
    • Прочитать уйму умных книг
    • Посмотреть на ужас Java и C++
    • Понять прототипы из JS, протоколы из TypeScript
    • Выучить SmallTalk
    !37

    View Slide

  42. Что понять ООП и писать
    нормально, нам нужно:
    • Прочитать уйму умных книг
    • Посмотреть на ужас Java и C++
    • Понять прототипы из JS, протоколы из TypeScript
    • Выучить SmallTalk
    • Поработать с акторами и Erlang. Выслушать тонну
    критики
    !37

    View Slide

  43. Что понять ООП и писать
    нормально, нам нужно:
    • Прочитать уйму умных книг
    • Посмотреть на ужас Java и C++
    • Понять прототипы из JS, протоколы из TypeScript
    • Выучить SmallTalk
    • Поработать с акторами и Erlang. Выслушать тонну
    критики
    • Понять, что ООП нужно только для очень высокого
    уровня абстракции в некоторых типах систем
    !37

    View Slide

  44. View Slide

  45. FP

    View Slide

  46. OOP
    FP

    View Slide

  47. OOP
    FP
    WTF ?!

    View Slide

  48. Вот бы можно было
    вставить нужное
    поведение...

    !39

    View Slide

  49. class RecommendedBooks(object):
    def __init__(self, book_repo, filter_api, cache):
    self._repo = book_repo
    self._api = filter_api
    self._cache = cache
    def __call__(self, books):
    book_names = self._repo.names_by_ids(books)
    good_books = self._api.by_preferences(book_names)
    recommended = self._cache.not_shown(good_books)
    return recommended
    !40

    View Slide

  50. Но нам нужно понять
    ограничения...

    !41

    View Slide

  51. Может быть типы?

    !42

    View Slide

  52. НО В ПИТОНЕ НЕТ
    ТИПОВ

    !43

    View Slide

  53. int + int = int

    !44

    View Slide

  54. В ПИТОНЕ ЕСТЬ ТИПЫ

    !45

    View Slide

  55. Типы курильщика Типы здорового человека
    uint
    double
    &mut str uint16_t
    std::boxed::Box
    List[int]
    data Bool = False | True
    float
    Callable[[int], int]
    IO[Result[User, Exception]]

    View Slide

  56. book_names = self._repo.names_by_ids(books)
    !47

    View Slide

  57. class RecommendedBooks(object):
    def __init__(
    self,
    book_repo,
    filter_api,
    cache,
    ):
    self._repo = book_repo
    self._api = filter_api
    self._cache = cache
    def __call__(self, books):
    book_names = self._repo(books)
    good_books = self._api(book_names)
    recommended = self._cache(good_books)
    return recommended
    !48

    View Slide

  58. class RecommendedBooks(object):
    def __init__(
    self,
    book_repo: Callable[[List[int]], List[str]],
    filter_api,
    cache,
    ) -> None:
    self._repo = book_repo
    self._api = filter_api
    self._cache = cache
    def __call__(self, books: List[int]):
    book_names = self._repo(books)
    good_books = self._api(book_names)
    recommended = self._cache(good_books)
    return recommended
    !49

    View Slide

  59. class RecommendedBooks(object):
    def __init__(
    self,
    book_repo: Callable[[List[int]], List[str]],
    filter_api: Callable[[List[str]], List[Book]],
    cache,
    ) -> None:
    self._repo = book_repo
    self._api = filter_api
    self._cache = cache
    def __call__(self, books: List[int]):
    book_names = self._repo(books)
    good_books = self._api(book_names)
    recommended = self._cache(good_books)
    return recommended
    !50

    View Slide

  60. class RecommendedBooks(object):
    def __init__(
    self,
    book_repo: Callable[[List[int], List[str]],
    filter_api: Callable[[List[str]], List[Book]],
    cache: Callable[[List[Book]], List[Book]],
    ) -> None:
    self._repo = book_repo
    self._api = filter_api
    self._cache = cache
    def __call__(self, books: List[int]) -> List[Book]:
    book_names = self._repo(books)
    good_books = self._api(book_names)
    recommended = self._cache(good_books)
    return recommended
    !51

    View Slide

  61. Но класс - не
    функция!

    !52

    View Slide

  62. @final
    @attr.dataclass(frozen=True, slots=True)
    class RecommendedBooks(object):
    _repo: Callable[[List[int]], List[str]]
    _api: Callable[[List[str]], List[Book]]
    _cache: Callable[[List[Book]], List[Book]]
    def __call__(self, books):
    book_names = self._repo(books)
    good_books = self._api(book_names)
    recommended_books = self._cache(good_books)
    return recommended_books
    !53

    View Slide

  63. SRP Callable class

    !54

    View Slide

  64. pip install punq
    !55

    View Slide

  65. !56

    View Slide

  66. import punq
    # Types:
    BookNamesByIds = Callable[[List[int]], List[str]]
    FilterPreferedBooks = Callable[[List[str]], List[Book]]
    NotShownBooks = Callable[[List[Book]], List[Book]]
    !56

    View Slide

  67. import punq
    # Types:
    BookNamesByIds = Callable[[List[int]], List[str]]
    FilterPreferedBooks = Callable[[List[str]], List[Book]]
    NotShownBooks = Callable[[List[Book]], List[Book]]
    # DI:
    container = punq.Container()
    container.register(BookNamesByIds, my_book_repo)
    container.register(FilterPreferedBooks, my_api)
    container.register(NotShownBooks, my_cache)
    !56

    View Slide

  68. import punq
    # Types:
    BookNamesByIds = Callable[[List[int]], List[str]]
    FilterPreferedBooks = Callable[[List[str]], List[Book]]
    NotShownBooks = Callable[[List[Book]], List[Book]]
    # DI:
    container = punq.Container()
    container.register(BookNamesByIds, my_book_repo)
    container.register(FilterPreferedBooks, my_api)
    container.register(NotShownBooks, my_cache)
    # Somewhere:
    container.resolve(RecommendedBooks)([1, 2, 3])
    !56

    View Slide

  69. Ключевой вывод
    Композиция позволяет писать сложную
    реализацию, абстракция оставляет ее простой
    для использования
    57

    View Slide

  70. А где тут dry-
    python?

    !58

    View Slide

  71. Callable[[List[int]], List[str]]
    !59

    View Slide

  72. Callable[[List[int]], List[str]]
    !60

    View Slide

  73. Callable[[List[int]], List[str]]
    • Выбросит ли она исключение?
    !60

    View Slide

  74. Callable[[List[int]], List[str]]
    • Выбросит ли она исключение?
    • Будет ли добиться в базу или http?
    !60

    View Slide

  75. dry-python/returns
    Делаем неявное – явным
    61

    View Slide

  76. Callable[[List[int]], List[str]]
    !62

    View Slide

  77. Callable[[List[int]], List[str]]
    • Выбросит ли она исключение? Да?
    Result[List[int], Exception]
    !62

    View Slide

  78. Callable[[List[int]], List[str]]
    • Выбросит ли она исключение? Да?
    Result[List[int], Exception]
    • Будет ли добиться в базу или http? Да?
    IO[List[int]]
    !62

    View Slide

  79. Callable[[List[int]], List[str]]
    • Выбросит ли она исключение? Да?
    Result[List[int], Exception]
    • Будет ли добиться в базу или http? Да?
    IO[List[int]]
    • Все вместе? Конечно!
    IO[Result[List[int], Exception]]
    !62

    View Slide

  80. И более удобная
    композиция
    compose, pipe, box, pipeline, lift
    63

    View Slide

  81. class FetchUserProfile(object):
    def __call__(self, user: int) -> IO[Result['User', Error]]:
    return pipe(
    user_id,
    self._make_request,
    IO.lift(box(self._parse_json)),
    )
    @impure
    @safe
    def _make_request(self, user_id: int) -> Response:
    ...
    @safe
    def _parse_json(self, response: Response) -> 'User':
    ...
    !64

    View Slide

  82. 65

    View Slide

  83. Версия 0.12
    !66

    View Slide

  84. !67

    View Slide

  85. Legacy-first
    !68

    View Slide

  86. github.com/life4/flakehell
    !69

    View Slide

  87. opencollective.com/
    wemake-python-styleguide
    !70

    View Slide

  88. opencollective.com/
    wemake-python-styleguide
    • 4 человека
    !70

    View Slide

  89. opencollective.com/
    wemake-python-styleguide
    • 4 человека
    • 3 компании: wemake.services, dry-labs, ПАО
    "Ростелеком"
    !70

    View Slide

  90. opencollective.com/
    wemake-python-styleguide
    • 4 человека
    • 3 компании: wemake.services, dry-labs, ПАО
    "Ростелеком"
    • Вы!
    !70

    View Slide

  91. Сегодня мы многое поняли

    View Slide

  92. Полезные ссылки
    • https://guide.elm-lang.org/architecture/
    • https://blog.ploeh.dk/2016/03/18/functional-
    architecture-is-ports-and-adapters/
    • https://fsharpforfunandprofit.com/rop/
    • https://www.destroyallsoftware.com/screencasts/
    catalog/functional-core-imperative-shell
    • https://www.youtube.com/watch?v=o9pEzgHorH0
    • https://sobolevn.me/
    !72

    View Slide

  93. tlg.name/
    opensource_findings
    !73

    View Slide

  94. Вопросы?
    github.com/sobolevn
    sobolevn.me
    74

    View Slide