Улучшение Python-кода: 12 советов для начинающих

от автора

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

То же самое относится и к тестовым задачам, выдаваемым кандидатам на должность программиста. Как известно, мы, когда впервые видим человека, формируем первое впечатление о нём за 30 секунд. Это влияет на то, как мы будем, в дальнейшем, оценивать этого человека. Мне кажется несправедливым то, что люди, обладающие привлекательной внешностью, добиваются всего легче, чем все остальные. То же самое применимо и к коду. Некто смотрит на чей-то проект и что-то тут же бросается ему в глаза. Ошмётки старого кода в репозитории — это как крошки хлеба, застрявшие в бороде после завтрака. Они могут напрочь испортить первое впечатление. Может, бороды у вас и нет, но, думаю, вам и так всё ясно.

Обычно легко понять то, что некий код написан новичком. В этом материале я дам вам несколько советов о том, как обыграть кадровиков вроде меня и повысить свои шансы на получение приглашения на собеседование. При этом вас не должна мучить мысль о том, что, применяя эти советы, вы кого-то обманываете. Вы не делаете ничего дурного. Применяя те небольшие улучшения кода, о которых пойдёт речь, вы не только повышаете свои шансы на успешное прохождение собеседования, но и растёте как программист. Не могу сказать, что профессиональному росту способствует упор на заучивание алгоритмов или модулей стандартной библиотеки.

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

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

Поговорим о том, как повысить качество ваших Python-проектов. Советы, которыми я хочу поделиться, улучшат ваш код. А если вы не сделаете из них карго-культ, то они ещё и помогут вашему профессиональному росту.

1. Уберите из репозитория ненужные файлы

Откройте страницу своего репозитория на GitHub. Есть ли в нём файлы с расширениями .idea, .vscode, .DS_Store или .pyc? Попали ли туда файлы из виртуального окружения? Если так — избавьтесь от всего этого и добавьте записи о соответствующих файлах и папках в .gitignore. Выкладывая код на GitHub следует придерживаться правила, в соответствии с которым в репозиторий не должно попадать ничего такого, что создано не владельцем репозитория. Вот хорошее руководство по .gitignore, в котором даётся обзор того, что обычно не стоит включать в репозитории.

▍Примеры

Начальный вариант файла .gitignore для Python-проектов

Следующий текст можно рассматривать в качестве начального варианта содержимого .gitignore. Добавьте такой файл в свой проект в самом начале работы над ним.

*.pyc *.egg-info  # Если вы программируете на Mac .DS_Store  # Если вы пользуетесь виртуальными окружениями # в проектах. Я, например, обычно ими пользуюсь. /env  # Настройки и хранение секретных данных (подробнее об этом - в следующем разделе) /.env 

Если вам нужен более масштабный пример .gitignore — взгляните на этот файл из коллекции GitHub. Используйте его как источник вдохновения и как базу для вашего .gitignore.

2. Не храните в коде секретные данные

В репозитории не должно быть никаких паролей к базам данных, ключей к внешним API, секретных ключей систем шифрования! Подобные вещи надо хранить в конфигурационных файлах или в переменных окружения. Ещё один вариант — их чтение из защищённого хранилища. А включать их в код — это в высшей степени неправильно. Вот — отличное руководство на тему хранения конфигурационных данных, подготовленное в рамках проекта The Twelve-Factor App (другие материалы этого проекта тоже весьма полезны).

▍Примеры

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

Ниже приведён фрагмент Flask-приложения. Автор хранит реквизиты для доступа к базе данных в коде.

from flask import Flask  app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql://user:secret@localhost:5432/my_db" 

Правильно: реквизиты хранятся в переменных окружения

Перенести реквизиты для доступа к базе данных в переменные окружения совсем несложно:

import os from flask import Flask  app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("SQLALCHEMY_DATABASE_URI") 

Теперь нужно, перед запуском приложения, инициализировать переменные окружения:

export SQLALCHEMY_DATABASE_URI=postgresql://user:secret@localhost:5432/my_db flask run 

Правильно: реквизиты хранятся в файле .env

Для того чтобы перед запуском программы не приходилось бы вручную инициализировать переменные окружения, можно пойти дальше. А именно, речь идёт о том, чтобы сохранить эти данные в файле .env. Далее, нужно установить пакет python-dotenv и инициализировать переменные окружения прямо из Python-кода.

Вот как может выглядеть файл .env:

SQLALCHEMY_DATABASE_URI=postgresql://user:secret@localhost:5432/my_db 

Вот как работать с этим файлом из кода:

import os from dotenv import load_dotenv from flask import Flask  load_dotenv()  app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("SQLALCHEMY_DATABASE_URI") 

И надо не забыть добавить запись об .env в .gitignore. Благодаря этому данный файл не будет случайно выгружен в репозиторий.

3. Добавьте в репозиторий файл README

В проекте, на его верхнем уровне, должен присутствовать файл README, в котором описана цель создания проекта, в котором даются инструкции по установке проекта и по началу работы с ним. Если вы не знаете о том, что писать в таком файле, обратитесь к руководству, размещённому на сайте Make a README.

▍Примеры

Файл README для Python-проекта

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

# Foobar  Foobar is a Python application for dealing with word pluralization.  ## Installation  Clone the repository from GitHub. Then create a virtual environment, and install all the dependencies.  ```bash git clone https://github.com/username/foobar.git python3 -m venv env source env/bin/activate python -m pip install -r requirements.txt ```  ## Usage  Initialize the virtual environment, and run the script  ```bash source env/bin/activate ./pluralize word words ./pluralize goos geese ```  ## Contributing  Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.  Please make sure to update the tests as appropriate.  ## License  [MIT](https://choosealicense.com/licenses/mit/) 

4. Если вы используете сторонние библиотеки — добавьте в репозиторий файл requirements.txt

Если в проекте используются сторонние зависимости, об этом нужно сообщить. Легче всего это сделать, создав файл requirements.txt в корневой директории проекта. В каждой строке этого файла приводятся сведения об одной зависимости. Нужно, кроме того, добавить инструкции по работе с этим файлом в README. Подробности о requirements.txt можно найти в руководстве пользователя по pip.

▍Примеры

Файл requirements.txt для Flask-приложения

Добавление файла requirements.txt в корневую директорию проекта — это самый лёгкий способ отслеживания зависимостей. Можно, помимо сведений о самих зависимостях, дать сведения и об их версиях. Вот пример файла requirements.txt:

gunicorn Flask>=1.1 Flask-SQLAlchemy psycopg2 

Указание более подробных сведений о зависимостях с использованием файла requirements.in

При работе над любым проектом всегда полезно иметь возможность воспроизведения его окружения. В результате, даже если вышла новая версия какой-нибудь библиотеки, можно использовать старую, проверенную в деле версию, работая с ней до тех пор, пока не будет решено перейти на новую. Это называется «фиксацией зависимостей». Легче всего это можно сделать, прибегнув к pip-tools. При таком подходе в вашем распоряжении окажется два файла: requirements.in и requirements.txt. Второй из них при этом вручную не модифицируют, просто добавляя его в репозиторий вместе с requirements.in. Вот как выглядит файл requirements.in:

gunicorn Flask>=1.1 Flask-SQLAlchemy psycopg2 

Для того чтобы на основе этого файла был бы автоматически создан requirements.txt, файл requirements.in компилируют, используя команду pip-compile. Вот как выглядит автоматически сгенерированный файл requirements.txt:

# # This file is autogenerated by pip-compile # To update, run: # #    pip-compile # click==7.1.2              # via flask flask-sqlalchemy==2.4.4   # via -r requirements.in flask==1.1.2              # via -r requirements.in, flask-sqlalchemy gunicorn==20.0.4          # via -r requirements.in itsdangerous==1.1.0       # via flask jinja2==2.11.2            # via flask markupsafe==1.1.1         # via jinja2 psycopg2==2.8.6           # via -r requirements.in sqlalchemy==1.3.19        # via flask-sqlalchemy werkzeug==1.0.1           # via flask  # The following packages are considered to be unsafe in a requirements file: # setuptools 

Как видите, готовый файл содержит сведения о точных версиях всех зависимостей.

5. Форматируйте код с помощью black

Неоднородное форматирование кода не помешает ему нормально работать. Но если код хорошо отформатирован — это улучшит его читабельность и упростит его поддержку. Форматирование кода может и должно быть автоматизировано. Если вы пользуетесь VS Code, то можете увидеть рекомендацию по установке black в качестве автоматического средства форматирования исходного кода, написанного на Python. Форматирование кода производится при сохранении файлов. Кроме того, black можно установить самостоятельно и форматировать код, пользуясь средствами командной строки.

▍Примеры

Неправильно: неформатированный код

Код, приведённый ниже, тяжело читать и расширять.

def pluralize ( word ):     exceptions={ "goose":'geese','phenomena' : 'phenomenon'   }     if word in exceptions :         return exceptions [ word ]     return word+'s'  if __name__=='__main__' :     import sys     print ( pluralize ( sys.argv[1] ) ) 

Правильно: тот же самый код, отформатированный с помощью black

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

def pluralize(word):     exceptions = {"goose": "geese", "phenomena": "phenomenon"}     if word in exceptions:         return exceptions[word]     return word + "s"  if __name__ == "__main__":     import sys      print(pluralize(sys.argv[1])) 

6. Избавьтесь от ненужных команд импорта

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

▍Примеры

Неправильно: наличие в коде ненужных команд импорта

В этом фрагменте кода импортированный модуль os не используется:

import os  print("Hello world") 

Правильно: в коде нет ненужных команд импорта

Вышеприведённый код очень просто привести в приличный вид:

print("Hello world") 

7. Избавьтесь от ненужных переменных

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

▍Примеры

Неправильно: наличие в коде ненужной переменной:

Здесь переменная response не используется:

def ping(word):     response = requests.get("https://example.com/ping") 

Правильно: в коде нет ненужных переменных

Тут нет ничего лишнего:

def ping(word):     requests.get("https://example.com/ping") 

8. Следуйте соглашению по именованию сущностей из PEP 8

Именование сущностей — это как форматирование. Неудачный выбор имён не помешает правильной работе программы, но затруднит работу с кодом. Кроме того, единообразный подход к именованию сущностей снимает с программиста нагрузку, связанную с постоянным выдумыванием имён. Почитать PEP 8 можно здесь.

▍Примеры

Правила именования сущностей из PEP 8

  • Имена файлов и директорий записываются в нижнем регистре с использованием символа подчёркивания для разделения слов: lowercase_underscores.
  • Так же составляют имена функций и переменных: lowercase_underscores.
  • Имена классов записывают с использованием «верблюжьего» стиля: CamelCase.
  • Имена констант записываются в верхнем регистре с использованием символа подчёркивания: UPPERCASE_UNDERSCORE.

Пример применения PEP 8

Ниже приведён фрагмент кода, имеющего достаточно сложную структуру, но соответствующего правилам PEP 8. Тут я, чтобы продемонстрировать именование разных сущностей, поместил простую функцию в класс.

#!/usr/bin/env python import sys  DEFAULT_NAME = "someone"   # <- UPPERCASE_UNDERSCORE  class GreetingManager:   # <- CamelCase      def say_hello(self, arguments):  # <- lowercase_underscores         if len(arguments) < 2:             target_name = DEFAULT_NAME         else:             target_name = arguments[1]   # <- lowercase_underscores         print(f"Hello, {target_name}")  if __name__ == "__main__":     GreetingManager().say_hello(sys.argv) 

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

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

Различные IDE и редакторы кода, вроде pycharm и VS Code, содержат встроенные линтеры и подсвечивают проблемные участки кода. Программист сам принимает решение о том, следовать этим рекомендациям или нет. Поначалу сообщения об ошибках, выдаваемые линтерами, могут показаться непонятными. Для того чтобы в них ориентироваться, стоит уделить некоторое время изучению используемого линтера. Это себя окупит.

Если говорить о линтерах, представленных инструментами командной строки, то в этой сфере я порекомендовал бы flake8. Этот линтер обладает разумными настройками, применяемыми по умолчанию. Обычно ошибки, о которых он сообщает, стоит исправлять. Если вы хотите строже относиться к своему коду — взгляните на pylint. Этот линтер способен выявлять множество ошибок, в число которых входят и те, о которых мы тут не говорим.

▍Примеры

Файл, который нужно почистить

В нижеприведённом коде (файл ping.py) можно увидеть некоторые проблемы и без применения линтера.

import requests import os  def PingExample():     result = requests.get("https://example.com/ping") 

Давайте проанализируем его с помощью flake8 и pylint.

Результаты анализа кода с помощью flake8

flake8 ping.py  ping.py:2:1: F401 'os' imported but unused ping.py:4:1: E302 expected 2 blank lines, found 1 ping.py:5:5: F841 local variable 'result' is assigned to but never used 

Результаты анализа кода с помощью pylint

pylint ping.py  ************* Module ping ping.py:1:0: C0114: Missing module docstring (missing-module-docstring) ping.py:4:0: C0103: Function name "PingExample" doesn't conform to snake_case naming style (invalid-name) ping.py:4:0: C0116: Missing function or method docstring (missing-function-docstring) ping.py:5:4: W0612: Unused variable 'result' (unused-variable) ping.py:2:0: W0611: Unused import os (unused-import) ping.py:2:0: C0411: standard import "import os" should be placed before "import requests" (wrong-import-order)  -------------------------------------------------------------------- Your code has been rated at -5.00/10 (previous run: -5.00/10, +0.00) 

10. Удалите из кода команды print, используемые при отладке

Отладка кода с использованием команд print, расположенных в его важнейших местах, — это нормально. Но не стоит, решив проблему, коммитить в репозиторий код, содержащий подобные команды.

▍Примеры

Неправильно: отладочные команды print в коде

Автор кода захотел узнать о том, к каким изменениям в файловой системе приведёт работа функции, сохраняющей объект в файл. Команды print, выполняемые до и после вызова тела функции, не решают никаких задач, имеющих отношение к самой функции. После того, как они помогли программисту разобраться в происходящем, их нужно удалить. Иначе они будут просто засорять код.

def serialize(obj, filename):     print("BEFORE", os.listdir())     with open(filename, "wt") as fd:         json.dump(obj, fd)     print("AFTER", os.listdir()) 

Правильно: код без ненужных команд print

Если убрать из кода отладочные команды — это уменьшит размер функции и повысит удобство работы с ней. А это всегда хорошо.

def serialize(obj, filename):     with open(filename, "wt") as fd:         json.dump(obj, fd) 

11. Не держите в репозитории закомментированный код

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

▍Примеры

Неправильно: ненужные комментарии в коде

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

name = input("What's your name: ") #short_name = name.split()[0] #if len(short_name) > 0: #    name = short_name print(f"Hello, {name}") 

Правильно: код, в котором нет ненужных комментариев

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

name = input("What's your name: ") print(f"Hello, {name}") 

12. Оформляйте скрипты в виде функций

В самом начале работы над программой её код обычно следует за потоком мыслей программиста. Этот код состоит из последовательности инструкций, решающих некую задачу. Выработайте у себя привычку оформлять последовательности инструкций в виде функций. Поступать так стоит с самого начала работы над проектом. Подобные функции нужно вызывать в самом конце программ, защитившись выражением if __name__ == «__main__». Это поможет вам использовать структурный подход при развитии проекта, извлекая из нужных мест вспомогательные функции. А позже, если надо, это облегчит оформление скриптов в виде модулей.

▍Примеры

Неправильно: скрипт, не оформленный в виде функции

Выполнение кода в этом примере начинается в первой строке и заканчивается в последней. Такой подход оправдан в том случае, если речь идёт о простых скриптах. Но если код скрипта окажется сложнее, воспринять его будет уже не так легко.

#!/usr/bin/env python name = input("What's your name: ") print(f"Hello, {name}") 

Правильно: скрипт, оформленный в виде функции

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

#!/usr/bin/env python  def say_hello():     name = input("What's your name: ")     print(f"Hello, {name}")  if __name__ == "__main__":     say_hello() 

Домашнее задание

Говорят, что мы запоминаем лишь 10% того, что прочли. Это значит, что вы запомните лишь один из 12 данных мной советов. Полагаю, это означает, что вы впустую потратили время, читая эту статью. А я, в таком случае, зря её писал.

Но, к счастью, есть одна хитрость. Известно, что практика позволяет сохранить около 80% знаний. Следовательно — вот вам задание: возьмите один из своих проектов и проанализируйте его с точки зрения моих 12 советов. У того, кто так и сделает, в 8 раз больше шансов профессионально вырасти, чем у того, кто просто прочтёт статью.

Есть ли в ваших Python-проектах недочёты, о которых говорит автор этой статьи?

ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/521602/