Тут – разные мелочи, на которых обычно спотыкаются новички в Питоне. Шагай свободно.
Раньше в C++ итерация по коллекции проходила так:
for(int i = 0; i < books_amount; i++) {
cout << books[i];
}
Этот же способ используется в других языках. Поэтому на Питоне хочется написать так же:
for i in len(books):
print(books[i])
Это неудобная дичь, древность и вообще. Вот как надо:
for book in books:
print(book)
Часто вместе с элементом нужен его номер. Памятуя, что можно итерировать по коллекции, хочется сделать как-то так:
i = 0
for book in books:
print(i, book)
i += 1
Это тоже неудобная дичь, древность и вообще. Для этого есть встроенная функция enumerate:
for book_number, book in enumerate(books):
print(book_number, book)
Делай правильно и не делай неправильно.
Часто нужно предусмотреть какой-нибудь хреновый случай – нет файла с данными, не работает интернет, не хватает места на диске, пользователь ввёл неверные данные.
В этом случае переменную с данными нужно чем-то заполнить, но со смыслом, типа, "тут ничего нет".
Для "ничего" в Питоне есть None. Не пустая строка и не -1, а именно None:
try:
latitude = float(input('Введите широту: '))
except ValueError:
latitude = None
if latitude is None:
print('wtf, dude?')
Обрати внимание на то, как проверяется, находится ли в переменной None: if latitude is None.
Не if latitude == None и не if latitude. Это важно.
Загрузим json из файла:
def load_json_data(filepath):
with open(filepath, 'r') as file_handler:
return json.load(file_handler)
Всё сломается, если передать путь до несуществующего файла. Исправим:
def load_json_data(filepath):
if os.path.exists(filepath):
with open(filepath, 'r') as file_handler:
return json.load(file_handler)
else:
return None
Первый секрет: если функция ничего не возвращает, то она возвращает None. Поэтому писать return None в конце
функции смысла нет.
Избавляемся от else:
def load_json_data(filepath):
if os.path.exists(filepath):
with open(filepath, 'r') as file_handler:
return json.load(file_handler)
Теперь всё лаконично, но очень связанно, как предложение, в котором, помимо деепричастных оборотов, есть ещё несколько уровней подчинений, сложным образом связанных друг с другом и заставляющие держать их все в памяти, чтобы понять смысл, пусть и простой.
Упростить можно так:
def load_json_data(filepath):
if not os.path.exists(filepath):
return None
with open(filepath, 'r') as file_handler:
return json.load(file_handler)
Теперь стало проще: меньше вложенности, просто читать. Меньше багов.
Часто в коде приходится проверять переменные на нулевые значения. Например, пустой список:
if len(users) == 0:
pass
Или пустая строка:
if user.email == '':
pass
Или ноль:
if user.level == 0:
pass
Все три примера выше – неверные. Вот их верные аналоги:
if not users:
pass
if not user.email:
pass
if not user.level:
pass
Дело в том, что любое условное выражение неявно конвертируется в boolean. Для каждого типа правила конвертации свои.
Например, любая строка превратится в True, кроме пустой. Любое число – тоже True, кроме нуля.
Подробнее в документации.
Это облегчает код и не вредит читаемости.
Стандартная библиотека Питона огромная, в ней куча всего полезного. Стоит глянуть на содержание, чтобы оценить масштаб.
Особое внимание советую уделить модулям os, collections, itertools и functools. Они позволяют сделать код ещё короче и более читаемым, а тебя – профессиональнее.
Другие модули тоже важны: стоит несколько раз прочитать про все, чтобы иметь представление о функциях и знать, где смотреть, если они понадобятся.
Названия должны однозначно говорить о том, зачем нужна сущность: переменная, функция или что-то ещё.
Переменные – это сущности, а их названия – это существительные (user.level) или их свойства (user.is_admin).
Функции что-то делают с переменными, значит их названия – глаголы (download_report, levelup_user).
Названия должны быть:
- понятными: понятно говорить о смысле. Не
result, аusers_onlineилиjson_content. - полными: никаких
rдля радиуса Земли илиiдля элемента списка. В первом случае подойдётearth_radius, а во втором какой-нибудьuserилиbook, в зависимости от того, что в списке. Длинные названия – не проблема, у всех давно есть автокомплит. - на английском: никаких
knigaилиpolsovatel. Брр. - грамотными: не поленись открыть переводчик и гугл, чтобы подобрать правильный перевод. Неправильный перевод создаёт ощущение неряшливости, а может и смыслу навредить – тогда о читаемости не может быть и речи.
Функции нужны, чтобы сделать код понятным и реиспользуемым.
Понятным – это когда с первого взгляда понятно, что он делает:
credentials = load_oauth_credentials_from_file('fb_creds.json')
fb_api = get_facebook_api(credentials)
messages = fb_api.get_unread_messages()
send_notifications_to_slack(messages=messages, user='ilebedev')
Сперва из файла загружаются ключи доступа к АПИ Фейсбука, потом создаётся объект для взаимодействия с АПИ и получаются непрочитанные сообщение. Эти сообщения отправляются в Слак пользователю ilebedev.
Достаточно проглядеть код сверху вниз и сразу понятно, что он делает. Если нужны детали – можно перейти к исходникам каждой функции. Они могут быть сложными, но тут этого не видно: код написан на английском.
Любой из этих кусков может пригодиться в других скриптах: например, доступ к АПИ можно хранить не только для Фейсбука, но и для Адводс или Вконтакте. Отправлять сообщения в Слак – тоже полезная функция, даже в отрыве от примера выше.
Такой код выглядит как конструктор: нашёл нужные функции, импортировал, вызвал, указал правильные аргументы – и готово.
Чтобы это работало, каждая функция должна делать что-то одно: load_oauth_credentials_from_file просто
загружает oauth-ключи, она не знает про Фейсбук и про то, что с помощью этих ключей будут получены сообщения.
Функции get_facebook_api всё равно, откуда к ней приехали credentials – из базы данных, файла или просто
из скрипта. send_notifications_to_slack ничего не знает о том, что messages к ней приехали от Фейсбука, для
неё это просто сообщения, которые надо отправить пользователю user.
Код нужен для того, чтобы им пользовались. Его цель – сделать пользователю удобно.
То, что задачи учебные и едва ли кто-то будет всерьёз ими пользоваться – не важно. Любой код должен быть удобен для пользователя.
Это значит, что у каждой задачи:
- должна быть документация. Что это, зачем, как запускать, какие файлы откуда надо скачать, что произойдёт и подобные вопросы в ней должны быть освящены. Это кажется ненужным ("кам он, это же учебные задачки"), но это не так. Доведение любой поделки до вменяемого состояния – такой же навык, как умение программировать и его необходимо развивать.
- не должно быть захардкоженых путей до файлов. Их же нет у пользователя! Напиши, где их взять, сделай путь параметром,
опиши в
--help, как им пользоваться. - не должно быть лишних обязательных параметров. Параметризировать – хорошо, но заставлять пользователя указывать все параметры – плохо. Лучше сделать необходимыми минимум параметров, а для остальных проставить значения по-умолчанию и написать об этом в документации.
- объяснять, что происходит. Если скрипт выводит друзей онлайн, он должен говорить, что это друзья пользователя, которые сейчас онлайн. Выводит самый большой бар – должен писать, что это – самый большой бар.