Pull to refresh

Best Practices для Python

Reading time6 min
Views35K

Мои личные наблюдения и правила

У всех разработчиков со временем нарабатывается опыт, растёт экспертиза. Когда вы много лет занимаетесь разработкой, приходит понимание каких-то общих концепций, вырабатываются правила поведения в конкретных сценариях.

В этой небольшой статье я хочу рассказать про свои Best Practices, которые я применяю и всегда советую другим. Сверху будут наиболее общие замечания, но чем ниже по статье вы будете продвигаться, тем больше будет реальных проблем и советов (с кодом и т. п.).

1) Чаще используйте генераторы

Многие опытные разработчики часто используют генераторы. И делают они это не просто так, генераторы позволяют значительно экономить память и ускорить (хоть и не так заметно) вашу программу. Если вы пишите большой и более-менее сложный код, то такая простая оптимизация лишней точно не будет. 

Если сомневаетесь в своих знаниях генераторов, то могу порекомендовать статью:  https://habr.com/ru/post/560300/

Если же у вас полно сложных вычислений или математического моделирования, то пожалуйста, не пилите свой велосипед (который ещё и будет страшно медленным и корявым), воспользуйтесь специальными библиотеками (NumPy, SciPy).

2) Соблюдайте правила игры

Говорю банальности, но всё-же. Не нарушайте PEP8, даже если очень хочется, выучите и поймите смысл DRY, KISS. Серьёзно, если вы будете придерживаться всех этих правил, то ваш код станет гораздо более читаемым. Не забывайте, ваш код может читать маньяк, который знает, где вы живёте)

А теперь без шуток. Очень часто разработчики решают опустить какое-нибудь правило из PEP8, или нагородить код с огромным Cognitive Complexity. Как потом поддерживать такой кусок кода? Правильно - никак. Поэтому постарайтесь никогда не нарушать правила Python-игры, иначе расплачиваться будет вся команда.

Кстати, немного облегчить задачу по приведению кода в порядок, смогут плагины по типу Black. Если не знаете что это - рекомендую ознакомиться.

3) Увеличивайте производительность вашего кода

Кто-то скажет: Зачем это делать? Python всё равно медленный, просто забьём. И как вы понимаете, забивать - плохая идея. Я надеюсь, что вам хоть немного жаль пользователей, которые вынуждены ждать несколько секунд, пока ваша программа обработает его запрос. 

Если это так, или вам просто дали задание увеличить скорость работы программы, то вот пару моих рекомендаций:

3.1) Многопроцессорность, а не многопоточность. В связи с особенностями работы GIL, вы не сможете в многопоточной системе полностью нагрузить вашу машину, программа будет тратить 5%-10% от максимальной мощности компьютера (возможно, когда-нибудь напишу статью про принцип и логику работы GIL). А вот при многопроцессорности, вы сможете получить максимальную производительность. Поэтому, сложные вычисления/расчёты и т. п. - многопроцессность.

3.2) Используйте слоты. В своей предыдущей статье я уже описывал что такое слоты, и как они работают. Тут только добавлю, что они хоть и не сильно, но всё же влияют на общую скорость работы вашей программы.

Моя предыдущая статья: https://vk.com/@matvey.chekashov-python-ispolzovanie-slots 

3.3) Экспериментируйте с интерпретаторами. Многие разработчики, по непонятной причине, бояться касаться интерпретатора и всё с ним связанное. Не будьте такими. Попробуйте применить PyPy, он ускоряет работу вашей программы в десятки раз (https://habr.com/ru/company/otus/blog/349230/)! 

Можете «потыкать» Cython, это расширенный Python с возможностью использовать C’шные компоненты (https://habr.com/ru/company/ruvds/blog/462487/).

Главное - экспериментируйте, сделайте свою программу по настоящему быстрой!

4) Тестируйте свой код

Ну вот, я свалился на совсем банальности, но делать нечего. Многие разработчики (и я в их числе) не любят тестироваться свой код. 

Писать тесты скучно, долго, муторно, да и просто бесит. Я вас понимаю, сам такой). Но тесты - необходимы. Без них вы не сможете быть уверены, что ваши изменения не обвалили рандомную часть системы. Плюс, тесты, в некотором роде, являются документацией к коду, смотря на них можно понять как работает программный компонент. 

Если коротко, тесты - душные, но без них никак. Так что привыкайте тестировать и будет вам счастье.

5) Используйте срезы (слайсы)

Все знают про срезы, но почему-то не так много разработчиков их использует. Люди городят циклы, условия, вместо банального использования a[:3]. Не бойтесь срезов, они работают быстро, выглядят лаконично. Также не забывайте про шаги, которые также можно указать в срезах.

Если не уверены в своих познаниях срезов, рекомендую ознакомиться.

6) Храните данные правильно 

Есть несколько разных способов хранить данные:

6.1) Просто передавать данные напрямую

Прямая передача данных
Прямая передача данных

Способ конечно рабочий, но немного кривой. Например: может поменяться адрес файла, тогда придётся во всём коде искать ссылку на файл и вручную всё это менять. 

6.2) Использовать константы

Использование констант
Использование констант

Такой способ хранения данных гораздо лучше. Если поменяется адрес файла, мы просто изменим одну константу и весь остальной код получит уже новую ссылку.

6.3) Использовать Дата-Классы

Лучше использовать Дата-Классы
Лучше использовать Дата-Классы

Я считаю это лучшим способом хранения данных. Почему? Например:

  • Автоматическая проверка типов, мы не сможем случайно записать в нашу переменную адреса файла число, или скажем кортеж;

  • Мы можем запретить изменение нашего dataclass, для этого в декораторе укажите frozen=True. Тогда у нас действительно получиться неизменяемая константа;

  • Так как это класс, то мы можем написать методы для удобного взаимодействия с нашими переменными, что может сократить кол-во повторяющегося кода.

Почитать и дата-классах можно тут.

7) Используйте максимум возможностей ООП

Из всех разработчиков пишущих на Python, ООП использует процентов 40, правильно использует ООП процентов 20, а использует все его возможности процентов 5.

Попадите в эти 5%, ООП - это круто, это удобно, это надёжно. 

Давайте кратко рассмотрим типичные проблемы в коде таких разработчиков:

7.1) Отсутствие абстракций. Это большая проблема, ведь абстракции позволяют нам писать программы не уходя в сторону и не реализуя бесполезный функционал (принцип YAGNI). 

Пример кода с абстракцией:

Абстракция
Абстракция

7.2) Постоянное использование наследования. Если вам нужен кусочек функционала из другого класса, то вы, как нормальный человек, не захотите всё писать заново. Не очень опытные разработчики просто наследуют класс, даже если это никак не сочетающиеся сущности (К примеру: вертолёт и голубь). Чтобы решить эту проблему, используйте композицию или агрегацию, подробнее про них можно прочесть тут.

7.3) Боязнь классов-в-классе и мета-классов. Когда разработчики впервые видят классы в классе или мета-классы (например в Django), у них встают волосы дыбом и начинает дёргаться глаз, но совершенно напрасно, ведь это, на самом деле, позволяет упростить код. 

Пример: 

Классы в классе
Классы в классе

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

8) Попробуйте кусочек функционального программирования

Как вы наверняка знаете, существует не только ООП, есть и другие парадигмы, среди них и ФП (функциональное программирование). В Python есть несколько функций из данной парадигмы, которые помогут немного упростить и сократить ваш код, рассмотрим на примере. Предположим, вам нужно перевести все данные из списка в строку, в классическом варианте это выглядит так: 

Уверен вам знаком такой способ. Он хорош, но зачем если можно проще? 

Использование map()
Использование map()

Согласитесь, такой код гораздо проще и понятнее. Функция map лишь одна из представителей ФП, более подробно по этой теме вы можете прочитать здесь.

9) Дескрипторы

Это достаточно сложная тема, но дающая огромную власть и величие :-)

С помощью дескрипторов можно автоматически проводить проверки, логировать, отправлять уведомления, вообщем всё что угодно. 

Так что же такое дескрипторы? Дескрипторы позволяют нам добавлять любую логику при создании объекта в классе, путём создания этого объекта через промежуточный класс который и называется дескриптором (вы могли такое видеть например в Django). Вот пример использования и создания дескрипторов:

Пример использования дескрипторов
Пример использования дескрипторов

В этом коде видно использование дескриптора, мы говорим, что атрибуты price и quantity будут проверяться с помощью дескриптора NoNegative. Думаю название говорящие, этот дескриптор не позволит нам создать отрицательное число, что позволяет нам убрать эти проверки из __init__. 

А вот и сам дескриптор: 

Пример дескриптора
Пример дескриптора

Как видите, ничего сложного тут нет. Если хотите более подробно ознакомиться с этой темой, рекомендую эту статью.

10) Декомпозируйте правильно

Я специально оставил эту тему для последнего, главного пункта.

Как же много разработчиков, знающих все описанные мною ранее темы, но при это всё равно пишущие ужасный код. 

Такая грустная ситуация возникает именно из-за декомпозиции. 

Для не знающих, декомпозиция - разделение большой задачи на ряд более простых. Такой подход поможет вам писать небольшие, элегантные, читаемые функции/классы/методы. Облегчит процесс тестирования. Упростит процесс сопровождения вашего кода другим разработчикам. Программист не умеющий или неправильно декомпозирующий, обречён писать трудный, монолитный, неподдерживаемый код.

Поэтому, первое чему вы должны научиться - декомпозиции, а потом уже всему описанному выше.


Благодарю за чтение моей небольшой статьи, пишите свои Best Practices, сделаем код чуть чище!

Мой GitHub: https://github.com/Ryize

Tags:
Hubs:
Total votes 36: ↑30 and ↓6+24
Comments48

Articles