PythonerrorsTypeErrorJSON

list indices must be integers: ошибка Python и JSON

Ошибка TypeError: list indices must be integers or slices, not str значит, что к списку обратились по строковому ключу. Почти всегда виноват JSON: разбор вернул список словарей, и вместо data['name'] нужно data[0]['name']. Разбираем диагностику за одну строку, зеркальную ошибку string indices и разницу с KeyError и IndexError.

8 мин чтенияСправочникPython · errors · TypeError · JSON · lists

TypeError: list indices must be integers or slices, not str значит, что ты обратился к списку по строковому ключу — как к словарю. Индексом списка может быть только целое число или срез, строка "name" туда не подходит.

Почти всегда это случается при разборе JSON: json.loads() или response.json() вернули список словарей, а в коде написано data["name"] вместо data[0]["name"]. Само сообщение называет виновника: слово list в начале строки говорит, что внешний объект — список, а не словарь.

Что означает list indices must be integers or slices, not str?

Python сообщает, что слева от квадратных скобок стоит список, а внутри скобок оказалась строка. Список хранит элементы по порядковым номерам, поэтому доставать из него можно только числом (data[0]), отрицательным числом (data[-1]) или срезом (data[:3]). Ключи вроде "name" понимает словарь — у него другое устройство. Ошибка при обращении к списку по строке возникает мгновенно, ещё до того, как Python посмотрит, есть ли вообще такое поле.

import json

raw = '[{"name": "Мария", "score": 91}, {"name": "Игорь", "score": 78}]'
data = json.loads(raw)

print(data["name"])
Traceback (most recent call last):
  File "app.py", line 6, in <module>
    print(data["name"])
          ~~~~^^^^^^^^
TypeError: list indices must be integers or slices, not str

Самая полезная часть сообщения — первое слово. list indices означает, что объект перед скобками — список. Если бы там была строка, Python написал бы string indices, если кортеж — tuple indices. То есть Python уже сказал тебе, какого типа объект у тебя в руках; остаётся признать это и подстроить обращение.

Главная причина: JSON вернул список словарей, а не словарь

Внешний уровень JSON — это либо объект {...}, либо массив [...]. Объект превращается в словарь Python, массив — в список. Документация API обычно показывает один элемент ответа, а сервер отдаёт массив таких элементов, и вот тут ломается интуиция: путь из документации ["items"]["title"] перестаёт работать, потому что между ними стоит ещё один уровень — номер элемента.

response = {
    "status": "ok",
    "items": [
        {"id": 17, "title": "Кофемолка", "price": 3990},
        {"id": 24, "title": "Термокружка", "price": 1290},
    ],
}

print(response["items"]["title"])
Traceback (most recent call last):
  File "app.py", line 9, in <module>
    print(response["items"]["title"])
          ~~~~~~~~~~~~~~~~~^^^^^^^^^
TypeError: list indices must be integers or slices, not str

response["items"] — это уже список, и следующий шаг обязан быть числом или циклом:

response = {
    "status": "ok",
    "items": [{"title": "Кофемолка", "price": 3990}, {"title": "Термокружка", "price": 1290}],
}

print(response["items"][0]["title"])
print(sum(item["price"] for item in response["items"]))
Кофемолка
5280

Вот та же мысль в виде таблицы «было — стало». Слева — то, что лежит в переменной, справа — рабочее обращение к полю.

Что в руках Не работает Работает
Список словарей [{...}, {...}] data["name"] data[0]["name"]
Словарь со списком внутри {"items": [...]} data["items"]["title"] data["items"][0]["title"]
Строка с JSON '[{"name": ...}]' data[0]["name"] json.loads(data)[0]["name"]
Строка из csv.reader ["17", "Кофемолка"] row["title"] row[1] или csv.DictReader

Почему ошибка появляется после json.load() или requests?

Потому что списки на верхнем уровне ответа — норма для API: список товаров, список пользователей, список сообщений. Пока ты работаешь со своим словарём, руками набранным в коде, всё предсказуемо. Как только данные приходят снаружи — из requests, из json.load(f) или из выгрузки — форму задаёшь не ты, а тот, кто эти данные отдал. TypeError при разборе JSON почти всегда означает ровно это: структура на один уровень глубже, чем ты предполагал.

Тот же эффект даёт csv.reader: он возвращает не словари, а списки строк, и row["title"] падает с той же ошибкой. Если нужны имена колонок — бери csv.DictReader, он читает заголовок файла и делает из каждой строки словарь.

import csv

lines = ["id,title,price", "17,Кофемолка,3990", "24,Термокружка,1290"]

for row in csv.DictReader(lines):
    print(row["title"], row["price"])
Кофемолка 3990
Термокружка 1290

DictReader принимает любой источник строк — и открытый файл, и список, как здесь, поэтому пример можно запустить как есть.

Как за одну строку понять, что у тебя в руках?

Не угадывай структуру — распечатай её. Три строки закрывают вопрос: type(data).__name__ покажет тип, len(data) — сколько там элементов, а list(data)[:5] — как выглядит первый кусок, не заливая терминал мегабайтом текста. Именно list(data)[:5], а не срез data[:1]: по словарю срез не работает и падает сам, а так одна и та же строка годится и для списка, и для словаря. Это надёжнее, чем печатать data целиком: в большом ответе глазами структуру не разобрать. Одно условие: этот print должен куда-то попасть. Если файл запускается двойным кликом, окно закроется раньше, чем ты успеешь прочитать и вывод, и traceback — чинится это способом запуска, разобранным в статье скрипт закрывается сразу после запуска.

import json

raw = '{"count": 2, "results": [{"name": "Мария"}, {"name": "Игорь"}]}'
data = json.loads(raw)

print(type(data).__name__)
print(list(data)[:5])

items = data["results"]
print(type(items).__name__, len(items))
print(items[0])
dict
['count', 'results']
list 2
{'name': 'Мария'}

Теперь видно всё: снаружи словарь с ключами count и results, внутри results — список из двух словарей. Правильный путь до имени — data["results"][0]["name"]. Если ответ API бывает то списком, то словарём, проверить это можно через isinstance (подробнее — в справочнике: проверка типов через isinstance):

def first_user(payload):
    if isinstance(payload, list):
        return payload[0]
    return payload["results"][0]

print(first_user([{"name": "Мария"}]))
print(first_user({"results": [{"name": "Игорь"}]}))
{'name': 'Мария'}
{'name': 'Игорь'}

Чем эта ошибка отличается от string indices must be integers?

Это соседние сообщения об одной и той же путанице, но с разной причиной. list indices значит, что JSON уже разобран и перед тобой список — не хватает числового индекса. string indices must be integers, not 'str' значит, что разбора вообще не было: в переменной лежит строка с текстом JSON, и Python честно пытается взять из неё символ по номеру. Лечение разное: в первом случае добавь индекс, во втором — вызови json.loads().

text = '[{"name": "Мария", "score": 91}]'   # это вернул f.read() или response.text

print(text[0]["name"])
Traceback (most recent call last):
  File "app.py", line 3, in <module>
    print(text[0]["name"])
          ~~~~~~~^^^^^^^^
TypeError: string indices must be integers, not 'str'

Здесь text[0] вернул один символ [, и уже у символа спросили ключ "name". Достаточно заменить f.read() на json.load(f) — и структура станет списком словарей. Та же ошибка ловится при работе с requests: response.text — это строка, а разобранная структура приходит из response.json().

Второй частый источник string indices — перебор словаря. for user in data по словарю выдаёт ключи, то есть строки, и user["name"] внутри цикла падает. Значения даёт data.values(), пары — data.items().

Списки индексируются числами и срезами — и больше ничем

Целое число, отрицательное число, срез — полный список допустимых индексов. Больше ничего список не примет: ни строку, ни дробное число.

prices = [199, 349, 990, 1290]
print(prices[0])
print(prices[-1])
print(prices[1:3])
print(prices[::2])
199
1290
[349, 990]
[199, 990]

Дробный индекс даёт почти такое же сообщение, только с другим типом в конце:

prices = [199, 349, 990, 1290]
print(prices[len(prices) / 2])
Traceback (most recent call last):
  File "app.py", line 2, in <module>
    print(prices[len(prices) / 2])
          ~~~~~~^^^^^^^^^^^^^^^^^
TypeError: list indices must be integers or slices, not float

Обычный / в Python всегда возвращает float, даже когда делится нацело. Для середины списка нужен //: prices[len(prices) // 2] вернёт 990. Как устроены срезы во всех вариантах — в справочнике: срезы списка.

Есть и зеркальная ситуация — когда список пытаются использовать как ключ словаря или элемент множества. Тогда Python скажет TypeError: unhashable type: 'list'; разбор этой ошибки — в статье unhashable type: 'list' — что значит ошибка в Python.

Как перебрать список словарей и достать поле?

Список словарей перебирают циклом for, и переменная цикла на каждом шаге — целый словарь, к которому уже можно обращаться по ключу. Никаких индексов вручную считать не нужно. Если требуется собрать одно поле со всех элементов, короче написать списковое включение, а если нужен один элемент по условию — next() с генератором и значением по умолчанию.

data = [{"name": "Мария", "score": 91}, {"name": "Игорь", "score": 78}]

for user in data:
    print(user["name"], user["score"])

names = [user["name"] for user in data]
print(names)

igor = next((u for u in data if u["name"] == "Игорь"), None)
print(igor)
Мария 91
Игорь 78
['Мария', 'Игорь']
{'name': 'Игорь', 'score': 78}

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

Чем TypeError здесь отличается от KeyError и IndexError?

Три сообщения выглядят похоже, но отвечают на разные вопросы. TypeError — неверный тип индекса: строкой в список лезть нельзя в принципе. KeyError — тип верный, объект действительно словарь, но такого ключа в нём нет. IndexError — тип верный, объект действительно список, но он короче, чем запрошенный номер. По сути Python подсказывает, на каком именно шаге твоя модель данных разошлась с реальностью.

Сообщение Что произошло Первый шаг
TypeError: list indices must be integers or slices, not str список индексируют строкой добавь номер: data[0]["name"]
TypeError: string indices must be integers, not 'str' JSON ещё не разобран, это строка json.loads(text) или response.json()
KeyError: 'email' это словарь, но ключа нет посмотри list(user) или используй user.get("email")
IndexError: list index out of range это список, но он короче проверь len(data)

Мягкий доступ к полям словаря (get, значения по умолчанию, setdefault) собран в справочнике: доступ к словарю и значения по умолчанию.

Частые ошибки

  1. Копировать путь из документации API, не проверив форму ответа. В примере показан один объект, а сервер отдаёт массив. Фикс: print(type(data).__name__, len(data)) перед первым обращением к полю.
  2. Брать response.text вместо response.json(). В руках останется строка, и ты получишь string indices must be integers, а не list indices. Фикс: разбирать ответ через response.json() или json.loads(response.text).
  3. Читать файл через f.read() вместо json.load(f). Та же строка вместо структуры, тот же результат.
  4. Перебирать словарь и ждать значений. for user in data по словарю даёт ключи-строки. Фикс: data.values() или data.items().
  5. Индексировать список тем, что пришло из input(). input() всегда возвращает строку, поэтому prices[n] падает. Фикс: prices[int(n)].
  6. Считать середину списка через /. Деление даёт float, и сообщение меняется на not float. Фикс: //.
  7. Обращаться к строке csv.reader по имени колонки. csv.reader отдаёт списки. Фикс: csv.DictReader или номер колонки.

Практика: разбери вложенный JSON в браузере

Теория закрепляется, только когда код запускается. На Python Arena задачи проверяются скрытыми тестами прямо в браузере, ставить ничего не нужно. Начни со списка словарей: Сложи одинаковые товары — там надо пройти по списку пар и собрать словарь-сводку, ровно та операция, на которой чаще всего и возникает эта ошибка. Дальше можно взять весь бесплатный блок задач на словари: задачи по словарям.

Как читать и писать JSON без сюрпризов, включая json.dump и json.load, показано в справочнике: сериализация JSON. И если при сохранении файла русские буквы вдруг превратились в последовательности вида \u041c\u0430\u0448\u0430, это не поломка — разбор в статье JSON в Python: русские буквы превратились в коды.

Мини-резюме

  • Сообщение list indices must be integers… значит: в квадратных скобках у списка стоит строка, а нужен номер или срез.
  • Первое слово сообщения — диагноз: list indices — у тебя список, string indices — у тебя ещё строка.
  • Главный источник — JSON: массив на верхнем уровне превращается в список словарей, поэтому data["name"] становится data[0]["name"].
  • Перед первым обращением к полю печатай type(data).__name__, len(data) и list(data)[:5] — это дешевле, чем угадывать.
  • string indices must be integers, not 'str' — это забытый json.loads() или response.json(), а не забытый индекс.
  • Список словарей перебирают циклом for: переменная цикла уже словарь, к ней можно обращаться по ключу.
  • TypeError — неверный тип индекса, KeyError — нет такого ключа, IndexError — список короче запрошенного номера.

Закрепи на практике

Решай задачи в Python-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр