- PVSM.RU - https://www.pvsm.ru -
В мои обязанности входит наём Python-разработчиков. Если у заинтересовавшего меня специалиста есть GitHub-аккаунт — я туда загляну. Все так делают. Может быть, вы этого и не знаете, но ваш домашний проект, не набравший ни одной GitHub-звезды, может помочь вам в получении работы.
То же самое относится и к тестовым задачам, выдаваемым кандидатам на должность программиста. Как известно, мы, когда впервые видим человека, формируем первое впечатление о нём за 30 секунд. Это влияет на то, как мы будем, в дальнейшем, оценивать этого человека. Мне кажется несправедливым то, что люди, обладающие привлекательной внешностью, добиваются всего легче, чем все остальные. То же самое применимо и к коду. Некто смотрит на чей-то проект и что-то тут же бросается ему в глаза. Ошмётки старого кода в репозитории — это как крошки хлеба, застрявшие в бороде после завтрака. Они могут напрочь испортить первое впечатление. Может, бороды у вас и нет, но, думаю, вам и так всё ясно.
Обычно легко понять то, что некий код написан новичком. В этом материале я дам вам несколько советов о том, как обыграть кадровиков вроде меня и повысить свои шансы на получение приглашения на собеседование. При этом вас не должна мучить мысль о том, что, применяя эти советы, вы кого-то обманываете. Вы не делаете ничего дурного. Применяя те небольшие улучшения кода, о которых пойдёт речь, вы не только повышаете свои шансы на успешное прохождение собеседования, но и растёте как программист. Не могу сказать, что профессиональному росту способствует упор на заучивание алгоритмов или модулей стандартной библиотеки.
В чём разница между новичком и более опытным разработчиком? Новичок не работал с устаревшими кодовыми базами. Поэтому он не видит ценности в том, чтобы вкладывать время в написание кода, который легко поддерживать. Часто новички работают в одиночку. Они, в результате, не особенно заботятся о читабельности кода.
Ваша цель заключается в том, чтобы показать то, что вас заботит читабельность вашего кода и возможность его поддержки в будущем.
Поговорим о том, как повысить качество ваших Python-проектов. Советы, которыми я хочу поделиться, улучшат ваш код. А если вы не сделаете из них карго-культ, то они ещё и помогут вашему профессиональному росту.
Откройте страницу своего репозитория на GitHub. Есть ли в нём файлы с расширениями .idea
, .vscode
, .DS_Store
или .pyc
? Попали ли туда файлы из виртуального окружения? Если так — избавьтесь от всего этого и добавьте записи о соответствующих файлах и папках в .gitignore
. Выкладывая код на GitHub следует придерживаться правила, в соответствии с которым в репозиторий не должно попадать ничего такого, что создано не владельцем репозитория. Вот [2] хорошее руководство по .gitignore
, в котором даётся обзор того, что обычно не стоит включать в репозитории.
Следующий текст можно рассматривать в качестве начального варианта содержимого .gitignore
. Добавьте такой файл в свой проект в самом начале работы над ним.
*.pyc
*.egg-info
# Если вы программируете на Mac
.DS_Store
# Если вы пользуетесь виртуальными окружениями
# в проектах. Я, например, обычно ими пользуюсь.
/env
# Настройки и хранение секретных данных (подробнее об этом - в следующем разделе)
/.env
Если вам нужен более масштабный пример .gitignore
— взгляните на этот [3] файл из коллекции GitHub. Используйте его как источник вдохновения и как базу для вашего .gitignore
.
В репозитории не должно быть никаких паролей к базам данных, ключей к внешним API, секретных ключей систем шифрования! Подобные вещи надо хранить в конфигурационных файлах или в переменных окружения. Ещё один вариант — их чтение из защищённого хранилища. А включать их в код — это в высшей степени неправильно. Вот [4] — отличное руководство на тему хранения конфигурационных данных, подготовленное в рамках проекта 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
. Далее, нужно установить пакет python-dotenv [5] и инициализировать переменные окружения прямо из 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
. Благодаря этому данный файл не будет случайно выгружен в репозиторий.
В проекте, на его верхнем уровне, должен присутствовать файл README
, в котором описана цель создания проекта, в котором даются инструкции по установке проекта и по началу работы с ним. Если вы не знаете о том, что писать в таком файле, обратитесь к руководству, размещённому на сайте Make a README [6].
Тут приведён пример файла 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/)
Если в проекте используются сторонние зависимости, об этом нужно сообщить. Легче всего это сделать, создав файл requirements.txt
в корневой директории проекта. В каждой строке этого файла приводятся сведения об одной зависимости. Нужно, кроме того, добавить инструкции по работе с этим файлом в README
. Подробности о requirements.txt
можно найти в руководстве [7] пользователя по pip
.
Добавление файла requirements.txt
в корневую директорию проекта — это самый лёгкий способ отслеживания зависимостей. Можно, помимо сведений о самих зависимостях, дать сведения и об их версиях. Вот пример файла requirements.txt
:
gunicorn
Flask>=1.1
Flask-SQLAlchemy
psycopg2
При работе над любым проектом всегда полезно иметь возможность воспроизведения его окружения. В результате, даже если вышла новая версия какой-нибудь библиотеки, можно использовать старую, проверенную в деле версию, работая с ней до тех пор, пока не будет решено перейти на новую. Это называется «фиксацией зависимостей». Легче всего это можно сделать, прибегнув к pip-tools [8]. При таком подходе в вашем распоряжении окажется два файла: 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
Как видите, готовый файл содержит сведения о точных версиях всех зависимостей.
Неоднородное форматирование кода не помешает ему нормально работать. Но если код хорошо отформатирован — это улучшит его читабельность и упростит его поддержку. Форматирование кода может и должно быть автоматизировано. Если вы пользуетесь VS Code, то можете увидеть рекомендацию по установке black
в качестве автоматического средства форматирования исходного кода, написанного на Python. Форматирование кода производится при сохранении файлов. Кроме того, black [9] можно установить самостоятельно и форматировать код, пользуясь средствами командной строки.
Код, приведённый ниже, тяжело читать и расширять.
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
гарантирует то, что переформатированный код будет работать так же, как его исходный вариант. Данный инструмент всего лишь снимает с программиста нагрузку по ручному форматированию кода.
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]))
Ненужные команды импорта обычно остаются в коде после каких-нибудь экспериментов и после рефакторинга. Если в программе не используется некий модуль, который раньше в ней применялся, не забудьте убрать из кода соответствующую команду импорта. Обычно редакторы подсвечивают неиспользуемые команды импорта, что облегчает их поиск и борьбу с ними.
В этом фрагменте кода импортированный модуль os
не используется:
import os
print("Hello world")
Вышеприведённый код очень просто привести в приличный вид:
print("Hello world")
То, о чём говорилось в предыдущем пункте, относится и к неиспользуемым переменным. Они могут попасть в код в те моменты, когда программист создаёт их, думая, что они могут пригодиться в дальнейшем, а потом оказывается, что они не нужны.
Здесь переменная response
не используется:
def ping(word):
response = requests.get("https://example.com/ping")
Тут нет ничего лишнего:
def ping(word):
requests.get("https://example.com/ping")
Именование сущностей — это как форматирование. Неудачный выбор имён не помешает правильной работе программы, но затруднит работу с кодом. Кроме того, единообразный подход к именованию сущностей снимает с программиста нагрузку, связанную с постоянным выдумыванием имён. Почитать PEP 8 можно здесь [10].
lowercase_underscores
.lowercase_underscores
.CamelCase
.UPPERCASE_UNDERSCORE
.Ниже приведён фрагмент кода, имеющего достаточно сложную структуру, но соответствующего правилам 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)
Линтер анализирует код и ищет в нём ошибки, которые можно обнаружить автоматически. Перед отправкой изменений в репозиторий код всегда полезно проверять с помощью линтера.
Различные IDE и редакторы кода, вроде pycharm и VS Code, содержат встроенные линтеры и подсвечивают проблемные участки кода. Программист сам принимает решение о том, следовать этим рекомендациям или нет. Поначалу сообщения об ошибках, выдаваемые линтерами, могут показаться непонятными. Для того чтобы в них ориентироваться, стоит уделить некоторое время изучению используемого линтера. Это себя окупит.
Если говорить о линтерах, представленных инструментами командной строки, то в этой сфере я порекомендовал бы flake8 [11]. Этот линтер обладает разумными настройками, применяемыми по умолчанию. Обычно ошибки, о которых он сообщает, стоит исправлять. Если вы хотите строже относиться к своему коду — взгляните на pylint [12]. Этот линтер способен выявлять множество ошибок, в число которых входят и те, о которых мы тут не говорим.
В нижеприведённом коде (файл ping.py
) можно увидеть некоторые проблемы и без применения линтера.
import requests
import os
def PingExample():
result = requests.get("https://example.com/ping")
Давайте проанализируем его с помощью flake8
и pylint
.
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 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)
Отладка кода с использованием команд print
, расположенных в его важнейших местах, — это нормально. Но не стоит, решив проблему, коммитить в репозиторий код, содержащий подобные команды.
Автор кода захотел узнать о том, к каким изменениям в файловой системе приведёт работа функции, сохраняющей объект в файл. Команды print
, выполняемые до и после вызова тела функции, не решают никаких задач, имеющих отношение к самой функции. После того, как они помогли программисту разобраться в происходящем, их нужно удалить. Иначе они будут просто засорять код.
def serialize(obj, filename):
print("BEFORE", os.listdir())
with open(filename, "wt") as fd:
json.dump(obj, fd)
print("AFTER", os.listdir())
Если убрать из кода отладочные команды — это уменьшит размер функции и повысит удобство работы с ней. А это всегда хорошо.
def serialize(obj, filename):
with open(filename, "wt") as fd:
json.dump(obj, fd)
Очищайте репозиторий от закомментированных старых версий кода и от закомментированного кода, который был написан для проведения каких-нибудь экспериментов. Если вы когда-нибудь решите вернуться к старой версии программы — это всегда можно сделать с помощью инструментов применяемой вами системы контроля версий. Остатки старого кода сбивают с толку тех, кто читает тексты программ. Такой код создаёт впечатление небрежного отношения к нему его автора.
Автор экспериментировал, прямо в коде программы, с преобразованием строк. Решено было не включать результаты этих экспериментов в итоговый вариант программы, но, на всякий случай, соответствующий код не удалили полностью, а лишь закомментировали.
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}")
В самом начале работы над программой её код обычно следует за потоком мыслей программиста. Этот код состоит из последовательности инструкций, решающих некую задачу. Выработайте у себя привычку оформлять последовательности инструкций в виде функций. Поступать так стоит с самого начала работы над проектом. Подобные функции нужно вызывать в самом конце программ, защитившись выражением if __name__ == «__main__»
. Это поможет вам использовать структурный подход при развитии проекта, извлекая из нужных мест вспомогательные функции. А позже, если надо, это облегчит оформление скриптов в виде модулей.
Выполнение кода в этом примере начинается в первой строке и заканчивается в последней. Такой подход оправдан в том случае, если речь идёт о простых скриптах. Но если код скрипта окажется сложнее, воспринять его будет уже не так легко.
#!/usr/bin/env python
name = input("What's your name: ")
print(f"Hello, {name}")
Поток выполнения программы начинается в последней строке кода — там, где вызывается функция say_hello()
. Если речь идёт о том, что в состав функции входит всего пара строк кода, то такой подход может показаться неоправданно усложнённым. Но это, в любом случае, облегчает изменение кода. Например, можно легко, воспользовавшись click [13], оснастить свою функцию возможностями по приёму параметров из командной строки.
#!/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-проектах недочёты, о которых говорит автор этой статьи?
Автор: ru_vds
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/razrabotka/357488
Ссылки в тексте:
[1] Image: https://habr.com/ru/company/ruvds/blog/521602/
[2] Вот: https://www.atlassian.com/git/tutorials/saving-changes/gitignore
[3] этот: https://github.com/github/gitignore/blob/master/Python.gitignore
[4] Вот: https://12factor.net/ru/config
[5] python-dotenv: https://pypi.org/project/python-dotenv/
[6] Make a README: https://www.makeareadme.com/
[7] руководстве: https://pip.pypa.io/en/stable/user_guide/#requirements-files
[8] pip-tools: https://pypi.org/project/pip-tools/
[9] black: https://black.readthedocs.io/en/stable/
[10] здесь: https://www.python.org/dev/peps/pep-0008/#prescriptive-naming-conventions
[11] flake8: https://flake8.pycqa.org/en/latest/
[12] pylint: https://pylint.pycqa.org/en/latest/
[13] click: https://click.palletsprojects.com/
[14] Источник: https://habr.com/ru/post/521602/?utm_source=habrahabr&utm_medium=rss&utm_campaign=521602
Нажмите здесь для печати.