Перейти к содержанию

172 уроков, 13 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 8 из 20 Начальный 30 мин 110 XP

Путь к значению: достаём данные из глубины

Цепочки data["user"]["name"] и data["items"][0]["price"], KeyError у отсутствующих полей и get с умолчанием — спуск по типичному ответу API без падений.

Редакция Питоники

JSON-ответ разобран в словарь ещё в уроке 7 — но это только полдела. Словарь с настоящими данными похож на многоэтажку: наверху пользователь, этажом ниже его корзина, в подвале — список товаров с ценами. Сегодня тренируем спуск: достаём из типичного ответа API имя, город и цену первого товара, а заодно учимся не падать, когда очередного этажа в данных нет.

Путь к значению записывается цепочкой квадратных скобок: data["user"]["name"]. Читается она слева направо, шаг за шагом: возьми по ключу user — там словарь; у него возьми ключ name — там строка. Каждая пара квадратных скобок в цепочке — один шаг вниз: по ключу словаря или по индексу списка. Данные API почти всегда устроены так, что путь можно продиктовать вслух: «у ответа возьми пользователя, у пользователя — имя».

Цепочка ключей: спуск по уровням

Возьмём типичный ответ API — профиль покупателя с корзиной. В живом проекте он приходит текстом и разбирается через json.loads; здесь он уже словарь, чтобы не отвлекаться. Смотрим, что лежит наверху и как вытащить вложенные поля.

типичный ответ API
data = {
    "user": {"name": "Анна", "city": "Казань"},
    "balance": 4200,
    "items": [
        {"title": "Клавиатура", "price": 3400},
        {"title": "Коврик", "price": 800},
    ],
}

print(type(data))
print(data["user"]["name"])
print(data["user"]["city"])
Вывод
<class 'dict'>
Анна
Казань

Разбираем: data["user"] — первый шаг, по ключу user лежит словарь с именем и городом. Второй шаг — ["name"] — достаёт из него строку. Наверху есть и другие ключи: balance с числом и items со списком товаров — до них дойдём через минуту. Полезный ориентир: тип значения подсказывает следующий шаг. Словарь — следующий шаг по ключу в кавычках, список — по номеру.

Глубина у настоящих ответов бывает приличная: у пользователя может лежать адрес, у адреса — улица, у улицы — индекс. Путь растёт вместе с данными: data["user"]["address"]["street"] — те же шаги, просто их больше. Страшного в длине ничего нет: каждый шаг проверяется печатью промежуточной переменной, и путь собирается как лесенка — с верхнего этажа вниз.

шаг за шагом - то же самое по частям
data = {
    "user": {"name": "Анна", "city": "Казань"},
    "balance": 4200,
}

user = data["user"]
name = user["name"]

print(user)
print(name)
print(data["user"]["name"] == name)
Вывод
{'name': 'Анна', 'city': 'Казань'}
Анна
True

Цепочка и пошаговый спуск — одно и то же: data["user"]["name"] разворачивается ровно в две переменные из примера. Пошаговая запись незаменима при отладке: напечатал промежуточный user — увидел все ключи этого этажа и понял, куда шагать дальше. Нашёл путь — свернул обратно в одну строку.

Список внутри ответа: доступ по индексу

Списки в API-ответах — товары, заказы, посты — нумеруются с нуля. Первый элемент — [0], второй — [1], последний удобно брать через [-1]. Внутри элемента снова словарь — и путь продолжается ключом.

товар по индексу
data = {
    "items": [
        {"title": "Клавиатура", "price": 3400},
        {"title": "Коврик", "price": 800},
    ]
}

print(data["items"][0]["title"])
print(data["items"][0]["price"])
print(data["items"][1]["title"])

print(data["items"][-1]["price"])
Вывод
Клавиатура
3400
Коврик
800

К списку применимы и срезы: data["items"][:2] — первые два товара, data["items"][1:] — всё, кроме первого. Срез возвращает новый список, и путь можно продолжать: len(data["items"]) посчитает товары, не трогая их содержимое. Это удобно для быстрых проверок: сколько вообще позиций прислал сервер, есть ли что разбирать.

Порядок шагов важен: сначала ключ, потому что наверху словарь; потом индекс, потому что внутри список; потом снова ключ. Перепутаешь шаги — Python честно сообщит, что по такому адресу ничего нет, но формулировка ошибки зависит от того, на каком именно шаге споткнулся.

порядок в цепочке важен
data = {"user": {"name": "Анна"}, "items": ["Клавиатура", "Коврик"]}

print(data["items"][0])
print(data["user"]["name"])

try:
    data[0]["name"]
except KeyError:
    print("KeyError: 0 - наверху словарь, у него ключи, а не индексы")
Вывод
Клавиатура
Анна
KeyError: 0 - наверху словарь, у него ключи, а не индексы

data[0] — это попытка достать из словаря значение по ключу 0, а такого ключа в ответе нет. Отсюда правило первой минуты: заглянул print(data) — посмотри, словарь наверху или список, и начинай путь с подходящего шага. Дальше цепочка сама подскажет: словарь — кавычки, список — номер.

Ключа нет: KeyError

Живые ответы коварны: поле может отсутствовать — телефон не указан, письмо не пришло, старая версия сервера не знает нового ключа. Прямое обращение по отсутствующему ключу роняет программу с KeyError, и хорошо, если это случилось на тестах, а не у пользователя.

KeyError: ключа нет
data = {"user": {"name": "Анна"}}

try:
    print(data["user"]["email"])
except KeyError as e:
    print("Нет ключа:", e)
Вывод
Нет ключа: 'email'

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

get: чтение с умолчанием

Метод .get(ключ) делает то же, что квадратные скобки, с одной разницей: если ключа нет, он не падает, а возвращает None — специальное значение «здесь ничего». Вторым аргументом можно назвать, что вернуть вместо None: .get(ключ, умолчание).

get с умолчанием
data = {"user": {"name": "Анна", "city": "Казань"}}

print(data["user"].get("city"))
print(data["user"].get("email"))
print(data["user"].get("email", "email не указан"))
print(data.get("balance", 0))
Вывод
Казань
None
email не указан
0

Обрати внимание на середину вывода: None — это «ключа нет», а строка ниже — «ключа нет, но договорились считать это пояснением». Умолчание выбирают по смыслу поля: для чисел — 0, для строк — пустая строка или подсказка, для списков — пустой список. Код после такого чтения продолжает работать, как будто поле просто оказалось нулевым.

Умолчание подбирают не наугад, а по типу поля. Для чисел — ноль: арифметика продолжится без проверок. Для строк — пустая строка, если значение склеится с другими, или пояснение, если оно попадёт в отчёт. Для списков — пустой список: цикл по нему просто не сделает ни шага. None годится, когда дальше по коду есть явная проверка: if email is None: ....

Вложенный get: защита на каждом уровне

Цепочка из get пишется естественно: каждый уровень возвращает словарь, у которого снова вызываем get. Если промежуточного уровня нет — вернётся пустой словарь {}, следующий get не найдёт в нём ключ и спокойно вернёт умолчание. Падать некому: на каждом шаге страхование уже встроено.

get-цепочка
data = {"user": {"name": "Анна"}}

city = data.get("user", {}).get("city", "не указан")
print(city)

email = data.get("user", {}).get("email", "не указан")
print(email)

phone = data.get("profile", {}).get("phone", "нет профиля")
print(phone)
Вывод
не указан
не указан
нет профиля

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

None не строка
data = {"user": {"name": "Анна"}}

try:
    email = data.get("email")
    print(email.upper())
except AttributeError:
    print("AttributeError: get вернул None, а не строку")
Вывод
AttributeError: get вернул None, а не строку

Есть и второй способ укорачивать цепочки — промежуточные переменные с осмысленными именами. user = data["user"], затем city = user.get("city", "..."): две строки читаются легче, чем одна длинная, а имя переменной документирует путь не хуже комментария. В коротких выражениях цепочка уместна, в длинных логиках побеждает лесенка из переменных.

ЗаписьЕсли ключа нетКогда использовать
data["user"]["email"]KeyError - программа падаетобязательные поля: без них ответ бесполезен
data["user"].get("email")Noneполе необязательное, дальше есть проверка
data["user"].get("email", "-")умолчаниеполе необязательное, нужен запасной вариант
data.get("user", {}).get("email", "-")умолчаниенеобязателен даже верхний уровень

Выбор записи — это выбор честности. Обязательное поле смело бери скобками: если сервер прислал ерунду, пусть программа громко падает на месте проблемы, а не тащит None дальше. Необязательное — только через get, чтобы ответ без фотографии или с пропавшим городом не превращался в аварию на проде.

Сводка по корзине: всё вместе

Соберём приёмы урока мини-отчёт по ответу API: имя и город пользователя, первый товар и сумму корзины. Путь к каждому значению — короткая цепочка из шагов, которые мы только что отработали. Перед каждым путём полезно проговорить три вопроса:

  • что наверху — словарь или список: от этого зависит первый шаг, ключ или индекс;
  • обязательное ли поле: скобки для обязательных, get для необязательных;
  • что показать при промахе: умолчание по типу поля — ноль, пустая строка или пояснение.
отчёт по ответу
response = {
    "user": {"name": "Анна", "city": "Казань"},
    "balance": 4200,
    "items": [
        {"title": "Клавиатура", "price": 3400},
        {"title": "Коврик", "price": 800},
    ],
}

name = response["user"]["name"]
city = response["user"].get("city", "не указан")
first = response["items"][0]
total = response["items"][0]["price"] + response["items"][1]["price"]

print(f"{name}, {city}: баланс {response['balance']}")
print(f"Первый товар: {first['title']} за {first['price']}")
print(f"Сумма корзины: {total}")
Вывод
Анна, Казань: баланс 4200
Первый товар: Клавиатура за 3400
Сумма корзины: 4200

Три строки вывода — три приёма: обязательные поля взяты скобками, город — get с умолчанием, цена первого товара — ключ, индекс, ключ. Кстати, баланс ровно равен сумме корзины: в учебных данных всё сговорилось, а вот сверять такое на живом ответе удобнее циклами из урока 10.

Мелочь, на которой спотыкаются в f-строках: внутри f"..." ключи словаря пишутся в одинарных кавычках — {response['balance']} — потому что двойные уже заняты самой строкой. Перепутать легко, но Python заметит сразу: строка оборвётся в неожиданном месте.

А вот как тот же путь выглядит в живом проекте, где ответ приносит библиотека requests. Фрагмент — для запуска на своём компьютере, в песочнице сети нет:

так это выглядит с живым запросом
# На своём компьютере, после pip install requests:
import requests

r = requests.get("https://api.example.com/profile")
data = r.json()                        # текст ответа сразу в словарь

name = data["user"]["name"]            # тот же путь, что мы тренировали
city = data.get("user", {}).get("city", "не указан")
Блок для запуска на своём компьютере: в браузерной песочнице сети нет. Путь к значениям — тот же, что мы отработали на словарях выше.

До живого запроса мы дойдём по маршруту раздела: сначала соберём правильный URL для API, потом научимся читать текст ответа. А ближайший навык уже зреет: ответы не всегда разбираются, и в уроке 9 разберём, как Python сообщает о битом JSON и как читать его ошибки, не паникуя.

Путь к значению в JSON диктуется вслух: у ответа — пользователь, у пользователя — имя; каждая пара скобок — шаг вниз по данным.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

data = {"a": {"b": [10, 20, 30]}}
print(data["a"]["b"][1])
data = {"user": {"name": "Иван"}}
print(data.get("cart", {}).get("total", 0))
data = {"items": [{"price": 100}, {"price": 250}]}
print(data["items"][-1]["price"] + data["items"][0]["price"])
Проверь себя
0 / 5

1. Что вернёт data["items"][0]["price"], если items — список товаров, а у первого цена 3400?

2. Что вернёт data.get("email"), если ключа email в словаре нет?

3. data.get("user", {}).get("city", "нет") — ключ user есть, ключа city нет. Что вернёт выражение?

4. Что произойдёт при data["user"]["email"], если ключа email нет?

5. Что означает 0 в записи data["items"][0]?

Карточки терминов
Запомнено: 0 / 6
Практика

Дан ответ API о заказе. Выведи имя покупателя (обязательное поле — скобками), город через .get с умолчанием «не указан» и цену последней позиции заказа через отрицательный индекс.

practice.py
Вопросы и ответы по уроку

Как достать значение из вложенного JSON в Python?

Цепочкой квадратных скобок: data["user"]["name"] — каждый шаг по ключу словаря, для списков внутри ответа — по индексу: data["items"][0]["price"]. Если поле необязательное, заменяй скобки на .get(ключ, умолчание), чтобы отсутствие поля не ронило программу с KeyError.

Чем отличается data.get("key") от data["key"]?

При отсутствии ключа скобки падают с KeyError, а get возвращает None (или второй аргумент — умолчание). Правило: обязательные поля читай скобками — пусть падение честно укажет на битый ответ; необязательные — только через get.

Как достать последний элемент списка из JSON-ответа?

Отрицательным индексом: data["items"][-1] — последний элемент, data["items"][-2] — предпоследний. Внутри элемента путь продолжается как обычно: data["items"][-1]["price"].

Почему data.get("email").upper() падает, хотя я использовал get?

Защитил только шаг чтения: если ключа нет, get вернул None, а у None нет строковых методов — отсюда AttributeError. Указывай умолчание нужного типа: data.get("email", "") — тогда upper() отработает на пустой строке.

Понравился урок? Сошлитесь на него

«Каждая пара квадратных скобок в цепочке — один шаг вниз: по ключу словаря или по индексу списка.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.