Путь к значению: достаём данные из глубины
Цепочки data["user"]["name"] и data["items"][0]["price"], KeyError у отсутствующих полей и get с умолчанием — спуск по типичному ответу API без падений.
Редакция Питоники
JSON-ответ разобран в словарь ещё в уроке 7 — но это только полдела. Словарь с настоящими данными похож на многоэтажку: наверху пользователь, этажом ниже его корзина, в подвале — список товаров с ценами. Сегодня тренируем спуск: достаём из типичного ответа API имя, город и цену первого товара, а заодно учимся не падать, когда очередного этажа в данных нет.
Путь к значению записывается цепочкой квадратных скобок: data["user"]["name"]. Читается она слева направо, шаг за шагом: возьми по ключу user — там словарь; у него возьми ключ name — там строка. Каждая пара квадратных скобок в цепочке — один шаг вниз: по ключу словаря или по индексу списка. Данные API почти всегда устроены так, что путь можно продиктовать вслух: «у ответа возьми пользователя, у пользователя — имя».
Цепочка ключей: спуск по уровням
Возьмём типичный ответ API — профиль покупателя с корзиной. В живом проекте он приходит текстом и разбирается через json.loads; здесь он уже словарь, чтобы не отвлекаться. Смотрим, что лежит наверху и как вытащить вложенные поля.
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, и хорошо, если это случилось на тестах, а не у пользователя.
data = {"user": {"name": "Анна"}}
try:
print(data["user"]["email"])
except KeyError as e:
print("Нет ключа:", e)
Нет ключа: 'email'
Ошибка называет, какого ключа не хватило: 'email' — это уже половина диагноза. Но ронять программу из-за необязательного поля не хочется. Для чтения без риска у словарей есть метод get — он делает шаг и в случае промаха возвращает не падение, а договорное значение.
get: чтение с умолчанием
Метод .get(ключ) делает то же, что квадратные скобки, с одной разницей: если ключа нет, он не падает, а возвращает None — специальное значение «здесь ничего». Вторым аргументом можно назвать, что вернуть вместо None: .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 не найдёт в нём ключ и спокойно вернёт умолчание. Падать некому: на каждом шаге страхование уже встроено.
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, но цепочка всё равно вернула умолчание и программу не уронила. Именно так достают необязательные поля из настоящих ответов, где структура плавает от версии к версии: у кого-то есть телефон, у кого-то — только имя.
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"])
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]?
Дан ответ API о заказе. Выведи имя покупателя (обязательное поле — скобками), город через .get с умолчанием «не указан» и цену последней позиции заказа через отрицательный индекс.
Как достать значение из вложенного 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-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
json · Урок 7
Вложенные структуры: списки в словарях и наоборот
Настоящие данные не плоские: список товаров внутри заказа, массив объектов в ответе API. Учимся читать вложенный JSON сверху вниз и не теряться в глубине.
json · Урок 9
Битый JSON: JSONDecodeError и как его читать
Одинарные кавычки, висячая запятая, True вместо true — три классические причины JSONDecodeError, и текст ошибки, который честно называет строку и колонку.
json · Урок 10
Обход данных: циклы по JSON-спискам
for по списку словарей, извлечение полей, суммирование выручки и list comprehension — превращаем выгрузку заказов в отчёт.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
BeautifulSoup / Scrapy · Урок 7
Сохранение результатов: CSV, JSON и базы данных
Список словарей собран — теперь доведём его до файла: CSV для таблиц, JSON для вложенных структур, sqlite3 для больших объёмов. Плюс две классические ловушки: пустые строки и кракозябры в Excel.
как сохранить результаты парсинга в csv jsonjson dump python
BeautifulSoup / Scrapy · Урок 8
Этичный парсинг: robots.txt, задержки и чтобы не забанили
Парсер собрал три тысячи страниц — а утром каждый запрос возвращает 403. Читаем robots.txt, представляемся по имени, держим вежливый темп и отвечаем на 429 так, чтобы дверь не закрылась навсегда.
robots txt парсингэтичный парсинг python
json · Урок 2
json.loads: чтение JSON из строки
Обратная дорога: текст JSON становится словарём Python одной командой json.loads — и по ключам можно ходить, считать и менять значения.
json loads pythonjson.loads