Полезные приёмы


python_basics

Тут – разные мелочи, на которых обычно спотыкаются новички в Питоне. Шагай свободно.

Итерируй сразу по коллекции, а не по индексам

Раньше в 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

Часто нужно предусмотреть какой-нибудь хреновый случай – нет файла с данными, не работает интернет, не хватает места на диске, пользователь ввёл неверные данные.

В этом случае переменную с данными нужно чем-то заполнить, но со смыслом, типа, "тут ничего нет".

Для "ничего" в Питоне есть 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)

Теперь стало проще: меньше вложенности, просто читать. Меньше багов.

Используй превращение типов в bool

Часто в коде приходится проверять переменные на нулевые значения. Например, пустой список:

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).

Названия должны быть:

Больше функций

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

Понятным – это когда с первого взгляда понятно, что он делает:

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.

Думай о пользователе

Код нужен для того, чтобы им пользовались. Его цель – сделать пользователю удобно.

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

Это значит, что у каждой задачи:

К оглавлению