$30 off During Our Annual Pro Sale. View Details »

Кодовый стиль python проектов в 2023 году

Denis Anikin
September 18, 2023

Кодовый стиль python проектов в 2023 году

Рассказал о том как эволюционировал кодовый стиль в нашем комьюнити и за его пределами. Пойдем от базового PEP8 и постараемся разобраться достаточно ли только его, что за линтеры нужны, какие актуальны, а какие не очень. Поговорим о том стоит ли писать документацию или нет, а так же чем нам может помочь во всём этом AI.

https://nastachku.ru/kodovyy-stil-python-proektov-v-2023-godu

Denis Anikin

September 18, 2023
Tweet

More Decks by Denis Anikin

Other Decks in Programming

Transcript

  1. /
    Денис Аникин
    https://xfenix.ru
    Кодовый стиль python
    проектов в 2023 году

    View Slide

  2. Денис Аникин
    2
    Что я делаю
    — работаю в Райфе
    — teamlead в 3 командах
    — community lead в Python Community
    — fullstack: typescript, python, devops
    — много раз выступал на конференциях и митапах
    https://xfenix.ru

    View Slide

  3. Немного слов в начале
    3
    —Я расскажу о том какой codestyle сейчас у нас (а мы работаем над ним постоянно,
    поэтому он может быть полезен) в community
    —Расскажу о том каким он был ранее
    —На основе этого расскажу как вам самостоятельно писать код по качеству лучше, чем в
    95%+ проектов, что существуют вокруг, как защититься от типовых ошибок
    —Даже если вы уже работаете в крупной организации, некоторые решения могут быть для
    вас новыми

    View Slide

  4. Что я понимаю под codestyle?
    4
    Это может быть не очевидно
    —Стандартные соглашения по тому как писать код
    —Стандартные способы предовтращения появления ошибок
    —Стандартные способы улучшения вашего кода
    —Стандартные инструменты, помогающие всего этого достигать
    —Типовые настройки для всего вашего рабочего процесса (от локальной среды до
    CI/CD конвейера)
    —И не только

    View Slide

  5. Зачем нам нужен codestyle
    — Снижение когнитивной нагрузки
    — Ускорение восприятия информации
    — Как следствие, возможность проще и легче поддерживать (делать новые
    части и не позволять старым «гнить») ваши проекты
    — Унификация (многие вещи понятны, даже если не вы их писали)
    — Устранение многих ошибок ещё до момента их возникновения

    View Slide

  6. Итак, сказочка!
    6

    View Slide

  7. Знакомьтесь!
    7

    View Slide

  8. View Slide

  9. Трицератослав.
    Древнерусский тимлид
    9

    View Slide

  10. А это его команда —
    10

    View Slide

  11. Junior
    у него свой меч в спине застрял
    Middle
    у него всё норм
    Senior
    именно он сжег деревню

    View Slide

  12. Они любят жечь
    деревни
    12

    View Slide

  13. Как же с ними делать
    продукт?
    13

    View Slide

  14. Договориться о
    правилах — code style,
    иначе…
    14

    View Slide

  15. View Slide

  16. А если не выйдет?
    16

    View Slide

  17. Трицератослав
    тимлид, сейчас микроменеджер

    View Slide

  18. Вы знаете рецепт —
    мы просто берем
    линтеры и размещяем
    их в пайплайнах
    18

    View Slide

  19. И вот теперь
    поговорим о том, что и
    как размещать
    19

    View Slide

  20. black
    20

    View Slide

  21. View Slide

  22. PEP8 и не только
    22
    Вместе c black
    —Стандартный кодовый стиль для питон проектов
    —Разработчики часто путают PEP и PEP8
    —Имеет смысл использовать его как стандарт по-умолчанию
    и не искать ничего лучшего

    View Slide

  23. Многие его не помнят.
    Я считаю, что это
    ошибка и
    PEP8 стоит заучить
    23

    View Slide

  24. Black
    24
    Если вы его еще не используете
    —Имеет мало опций настройки и это правильно (я не очень поддерживаю grey)
    —Можно конфигурировать длину линии, у нас стандарт 120
    —Проверяет, что код продуцирует валидное AST
    —За более чем 4 года работы в качестве автоматического инструмента у очень
    большого количества людей показал себя как действительно надежный инструмент

    View Slide

  25. А ещё вы можете
    сказать:
    our code is blacked
    25

    View Slide

  26. isort
    26

    View Slide

  27. И вновь PEP8
    27
    https://peps.python.org/pep-0008/#imports
    —Импорты надо разбивать на 3 группы: встроенные, сторонние библиотеки и свой код
    —Разделять стоит 1 пустой строкой
    —После этого блока 2 пустых строки

    View Slide

  28. View Slide

  29. Открытие 1:
    большинство
    нарушает это правило
    29

    View Slide

  30. Открытие 2:
    isort по-умолчанию
    настроен просто
    ужасно!
    30

    View Slide

  31. Типовая конфигурация isort
    31
    Ну и где здесь pep8?
    —FUTURE
    —STDLIB
    —THIRDPARTY
    —FIRSTPARTY
    —LOCALFOLDER

    View Slide

  32. isort default
    32
    from __future__ import absolute_import
    import os
    import sys
    from third_party import (lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8,
    lib9, lib10, lib11, lib12, lib13, lib14, lib15)
    from my_lib import Object, Object2, Object3
    print("Hey")
    print("yo")

    View Slide

  33. isort default — нормальные импорты
    33
    from __future__ import absolute_import
    import os
    import sys
    from third_party import (lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8,
    lib9, lib10, lib11, lib12, lib13, lib14, lib15)
    from my_lib import Object, Object2, Object3
    print("Hey")
    print("yo")

    View Slide

  34. isort default — а вот так вообще хорошо
    34
    from __future__ import absolute_import
    import os
    import sys
    import third_party
    import my_lib
    print("Hey")
    print("yo")

    View Slide

  35. Дополнительные соглашения по импортам
    35
    —Импортируйте встроенные модули целиком (особенно typing)
    —Если у вас 2 и более импортов из модуля — импортируйте его целиком
    —Стандартный pep8, но все таки: не импортируйте «звездочку»

    View Slide

  36. Конфигурация isort pyproject.toml
    36
    Для соблюдения PEP8
    [tool.isort]
    line_length = 120
    multi_line_output = 3
    include_trailing_comma = true
    lines_after_imports = 2
    lexicographical = true
    sections = ["FUTURE", "STDLIB", "FIRSTPARTY", "THIRDPARTY", "LOCALFOLDER"]
    no_lines_before = ["STDLIB", "THIRDPARTY"]
    known_third_party = []
    known_local_folder = []

    View Slide

  37. docformatter
    37

    View Slide

  38. Оказывается, одного
    PEP8 недостаточно.
    Нужен ещё PEP257
    38

    View Slide

  39. View Slide

  40. View Slide

  41. У этого инструмента
    есть режим inplace
    исправления кода
    41

    View Slide

  42. autoflake
    42

    View Slide

  43. autoflake
    43
    —Удаляет неиспользуемые переменные
    —Удаляет мертвые импорты
    —Тоже умеет inplace

    View Slide

  44. Если настроить
    автоматом, у вас
    никогда не будет
    мертвого кода
    44

    View Slide

  45. pyupgrade
    45

    View Slide

  46. Что представляет из себя pyupgrade
    46
    —Умеет переписывать код под новые версии python
    —Все так же умеет inplace
    —В постоянную работу, все же, взять его проблематично (если у вас больше 1 проекта)

    View Slide

  47. Я зову эту штуку
    «вечно свежий python»
    47

    View Slide

  48. pybetter
    48

    View Slide

  49. Код ещё лучше
    49
    —Внедряет некоторые хорошие практики написания кода (их всего 10, но они важные)
    —И тоже умеет inplace
    —Увы, слабо развивается

    View Slide

  50. 50
    Пример 1

    View Slide

  51. 51
    Пример 2

    View Slide

  52. eradicate
    52

    View Slide

  53. Все тоже
    53
    —Берет комментарий, eval’ит его и если тот eval’ится, то удаляет
    такой комментарий
    —Тоже умеет inplace

    View Slide

  54. View Slide

  55. View Slide

  56. Всё просто: при
    наличии git нет
    необходимости
    захламлять код
    56

    View Slide

  57. Last but not least:
    Pylint
    57

    View Slide

  58. Битва pylint vs flake8
    58
    Плюсы
    —На мой взгляд pylint содержит нереальное количество хороших
    практик и советов из коробки
    —Этот инструмент сразу даёт возможность писать как минимум
    хорошо
    —Для тех, кто устал от пачек линтеров, решение «всё в одном»

    View Slide

  59. Битва pylint vs flake8
    59
    А вот дальше идут минусы
    —Нереально медленный
    —Очень сложная конфигурация
    —Fix режима нет (а так можно было?)

    View Slide

  60. Картинка по запросу «конфигурация pylint»

    View Slide

  61. Аннотации типов
    61

    View Slide

  62. Лучший тайп чекер — mypy
    62
    Альтернатива ему — pyright
    —Сейчас, в 2023 году, имеет смысл включать strict по-умолчанию
    —Не забывайте о mypy . --install-types
    —Для pydantic можно подключить комплектный плагин
    —У моего коллеги, Миши Гурбанова, вышел доклад с подробным
    исследованием текущих тайпчекеров: https://pycon.ru/da-kto-takie-ehti-vashi-
    tajp-chekery

    View Slide

  63. Свежайшие решения
    (2023 год)
    63

    View Slide

  64. Мы выкинули все
    линтеры!
    64

    View Slide

  65. View Slide

  66. …и взяли Ruff
    66

    View Slide

  67. View Slide

  68. 68
    Ruff из Байкала помогает древним разработчикам писать хороший код!

    View Slide

  69. 69
    Деревни больше не горят!

    View Slide

  70. Что заменил
    собой ruff?
    70

    View Slide

  71. Это впечатляет
    71
    У вас есть почти всё, что вы вообще могли бы хотеть. И это на космической скорости
    —pylint
    —isort
    —около 30 плагинов flake8
    —perflint (устраняет некоторые анти-паттерны, убивающие производительность)
    —refurb (но частично)
    —pyupgrade
    —eradicate
    —pydocstyle
    —numpy, airflow, pandas-vet, flynt, mccabe, pep8-naming, pycodstyle
    —свои правила

    View Slide

  72. А теперь кое-что
    очень важное
    72

    View Slide

  73. View Slide

  74. Ruff исправляет.
    Ошибки.
    За вас!
    74

    View Slide

  75. View Slide

  76. Но если только вы
    включите все правила
    по-умолчанию
    76

    View Slide

  77. Как ruff
    сконфигурировать?
    77

    View Slide

  78. Пример CLI
    78
    ruff check .\
    --ignore=I,EM,FBT,TRY003,S101,D1,G004,FA,ANN101\
    --line-length=120\
    --fixable=ALL\
    --select=ALL

    View Slide

  79. Пример CLI
    79
    ruff check .\
    --ignore=I,EM,FBT,TRY003,S101,D1,G004,FA,ANN101\
    --line-length=120\
    --fixable=ALL\
    --select=ALL

    View Slide

  80. Таким образом, мы
    делаем ваш код
    потрясающим
    автоматически
    80

    View Slide

  81. Некоторые правила мы все же отключаем
    81
    Лишь небольшую часть
    —I так как передать конфигурацию глобально невозможно. Если вы
    индвидуально запускаете ruff в каждом проекте, то можно использовать
    pyproject.toml (информация уточняется)
    —TRY003 писать message в exception это нормально
    —D1 можно пропускать все докстринги
    —G004, если вы любите f-строки в логах
    —ANN101 можно отключать, если вы не хотите везде подписывать typing.Self,
    так как он нормально выводится mypy сейчас автоматически
    —EM если вы любите добавлять сообщения в ваши эксепшены
    —FBT довольно спорный набор правил, запрещающий bool аргументы

    View Slide

  82. Как pyproject.toml может выглядеть
    82
    Для конфигурации ruff (в бонус пресет для тестов)
    [tool.ruff]
    fix = true
    line-length = 120
    select = ["ALL"]
    ignore = ["D1", "D203", "D213", "FA102", "I”, “ANN001”]
    [tool.ruff.extend-per-file-ignores]
    "tests/*.py" = ["S101", "S311"]

    View Slide

  83. И это не всё
    83

    View Slide

  84. Скорость!
    84
    И это не обман

    View Slide

  85. Другие интересные нюансы
    85
    —Более 600 правил и число растет быстро
    —Есть кеширование
    —Дружит с монорепами

    View Slide

  86. Еще не перешли на
    ruff?
    86

    View Slide

  87. View Slide

  88. Я уже ставлю RUFF!

    View Slide

  89. Поставил pylint
    случайно :(

    View Slide

  90. Документация
    90

    View Slide

  91. 91
    Показал подруге объем документации на проекте

    View Slide

  92. View Slide

  93. View Slide

  94. Я раскрыл заговор
    94

    View Slide

  95. View Slide

  96. Документация
    96
    —Все считают, что она должна быть
    —Почти у всех её либо нет, либо она не актуальная (что хуже её
    отсутствия)
    —Всем всегда стыдно, что её нет и, почему-то, менее стыдно, что она не
    актуальная
    —Нет хороших подходов, есть спорные
    —Автодокументация не работает, её почти не читают
    —Спеки выглядят хорошо в начале дороги, после сотой в них не
    разобраться

    View Slide

  97. Может быть, нам не
    писать документацию?
    97

    View Slide

  98. View Slide

  99. Не переключайте
    канал!
    99

    View Slide

  100. Я предлагаю вам
    самодокументируемый
    код
    100

    View Slide

  101. View Slide

  102. Основные принципы
    102
    Они довольно простые
    —Вербозные названия всего: переменных, классов, функций, констант
    —Не используйте слово get в большинстве случаев
    —Меньше магических методов (лучше вообще без них)
    —Никаких чисел кроме 0 и 1 в коде, все остальное — только константы с человеческими
    именами
    —Используйте enum для константных перечислений
    —Функция должна именоваться глаголом, а не существительным. Она что-то делает!
    —Зен питона — не набор правил

    View Slide

  103. Полу-автоматический
    кодинг
    103

    View Slide

  104. Принцип 1: все что может
    быть сделано
    автоматом, должно быть
    сделано автоматом
    104

    View Slide

  105. Как этого достичь?
    105
    —Вам нужен vscode
    —Вам понадобятся стандартные плагины mypy, black и ruff, а так же,
    возможно, отдельностоящий isort

    View Slide

  106. 106
    Как выглядит конфигурация?
    "python.linting.mypyEnabled": true,
    "python.formatting.provider": "black",
    "python.formatting.blackArgs": [
    "--line-length=120",
    ],
    "[python]": {
    "editor.formatOnPaste": true,
    "editor.formatOnType": true,
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
    "source.fixAll": true,
    },
    },
    "ruff.args": [
    "--line-length=120",
    "--ignore=I,EM,FBT,TRY003,S101,D101,D102,D103,D104,D105,G004,D107,FA102",
    "--fixable=ALL",
    "--select=ALL"
    ],
    "emeraldwalk.runonsave": {
    "commands": [
    {
    "match": ".py",
    "cmd": "isort --line-length 120 --lines-after-imports 2 --no-lines-before stdlib,localfolder ${file}",
    "isAsync": true,
    }
    ]
    }

    View Slide

  107. Принцип 2: как можно
    больше AI утилит
    107

    View Slide

  108. Некоторые полезные решения
    108
    —Sourcery (правил меньше, чем у ruff, но несколько раз он находил что-то
    дополнительно). Пока пилотируем. Правил около 200
    —Tabnine
    —Или Code whisperer
    —Copilot пока лучше всех, но не работает без VPN и аккаунта за 100$
    —Можно использовать ChatGPT 3.5, Bard, GigaChat, TheB.AI

    View Slide

  109. Аннотации типов
    109

    View Slide

  110. Как стартануть?
    110
    Я знаю, может быть не просто
    —Берете ruff
    —Берете mypy
    —Включите режим strict в mypy
    —Включаете все правила в ruff
    —Они вас заставят J

    View Slide

  111. Стремитесь к покрытию кода в 100%
    Аннотации tips & tricks
    111
    —Освойте не только стандартные типы, но и Literal и особенно Final
    —Final имеет смысл аннотировать все переменные вообще по-умолчанию (чаще всего
    изменение переменных и ведет к ошибкам)
    —Примитивные типы можно не аннотировать (a = 5 не надо расписывать как a: int = 5)
    —Дженерики иногда полезны
    —Annotated помогает в валидации
    —сast, overload, final, never, noreturn, typealias, newtype, classvar, typeguard, unpack,
    Protocol, assert_type, assert_never, TYPE_CHECKING — очень полезные инструменты

    View Slide

  112. 112
    Пример с Final: до
    some_value = 5
    some_another_value = 'kek’
    for one_element in range(100):
    some_another_value = some_another_value * some_value
    some_value += 1

    View Slide

  113. 113
    Пример с Final: после
    import typing
    some_value: typing.Final = 5 # <- тут само выведет typing.Final[typing.Literal[5]]
    some_another_value: typing.Final = 'kek'
    for one_element in range(100):
    some_another_value = some_another_value * some_value # <- тайпчекер поймает
    some_value += 1

    View Slide

  114. Тонкое эстетическое
    восприятие отдельных
    людей
    114

    View Slide

  115. Мне не нравится как
    форматирует black…
    115

    View Slide

  116. 5 групп импортов в
    isort — достаточно
    логичны…
    116

    View Slide

  117. Я многое погашу в
    ruff/pylint, потому что
    не согласен…
    117

    View Slide

  118. View Slide

  119. Легко намекну вам —
    119

    View Slide

  120. View Slide

  121. Серьезные советы
    121
    Правда (тм)
    —Успокойте свое чувство эстетики, оно совершенно неуместно когда
    речь заходит о больших и дорогих проектах
    —Общий кодовый стиль и подход стоят гораздо больше, чем
    «красивые» кавычки или более веселые переносы
    —Если гасите правила в линтерах, то обязательно записывайте в
    глобальные командные установки почему вы это делаете
    —Ваши аргументы должны быть адекватными («мне не понравилось» —
    не такой аргумент)

    View Slide

  122. Дополнительно
    122

    View Slide

  123. Набор стандартных советов
    123
    —Всегда «сужайте» try-except блок до минимального количества строк (если
    возможно, до 1)
    —Всегда «сужайте» класс ошибки в except (не все знают, но это крайне
    полезная графическая иллюстрация стандартной иерархии ошибок
    https://docs.python.org/3/library/exceptions.html#exception-hierarchy)
    —Делайте ретраи, ограничивайте количество раз, делайте умный интервал
    и jitter (библиотека backoff поможет)
    —Храните всю конфигурацию линтеров в pyproject.toml
    —Избегайте вложенных и сложных условий с помощью инверсии
    —Используйте композицию и меньше наследования

    View Slide

  124. И кое-что ещё
    124
    — https://github.com/regebro/pyroma позволяет оценить «дружелюбность» вашего пакета
    — https://github.com/jendrikseipp/vulture ищет мертвый код по всему проекту
    — https://github.com/dropbox/pyannotate выводит аннотации для вашего кода (но работа
    довольно ручная)
    — https://github.com/JelleZijlstra/autotyping добавляет типовые аннотации (не идеально)
    — https://github.com/bndr/pycycle обнаруживает циклические импорты (тесты?)
    — https://github.com/Instagram/Fixit любопытный фреймворк для автоисправлений кода (!)
    — https://github.com/lyz-code/autoimport сам добавляет нужные импорты
    — https://github.com/pyupio/safety проверяет безопасность проектов

    View Slide

  125. Что по поводу SOLID & GRASP
    125
    —https://github.com/mschwager/cohesion проверяет связанность вашего кода
    —Mypy проверяет принцип Liskov substitution principle
    —Если вы хотите достичь DIP принципа, попробуйте паттерны IoC и DI: на
    pycon 2023 были посвященные этому доклады. Так же есть неплохие DI
    фреймворки, мне нравится dependency injector по своему, так же можно
    посмотреть di, rodi, lagom
    —Для некоторых других архитектурных линтеров советую доклад Коли
    https://pycon.ru/arhitektura-knuta-i-pryanika

    View Slide

  126. View Slide

  127. View Slide

  128. Теперь качества кода
    станет значительно
    лучше
    128

    View Slide

  129. View Slide

  130. Денис Аникин
    https://xfenix.ru
    https://github.com/xfenix/
    Вопросы?
    Поставьте лайк на гитхабе,
    пожалуйста

    View Slide