Тут – разные мелочи, на которых обычно спотыкаются новички в Питоне. Шагай свободно.
Раньше в 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
, как им пользоваться.