PythonMastery
reference 3 min read · lesson 4 of 60 in Errors

TypeError: list indices must be integers or slices, not str

1 · The lesson

read

What this error means

You indexed a list with a string, like users["name"]. You get list items by position (users[0], users[1]), not by key. You can only use a string subscript on a dict. Python is telling you it expected an integer position (or a slice like users[0:2]) and got a string instead.

When you see it

python
Traceback (most recent call last):
  File "main.py", line 4, in <module>
    name = users["name"]
TypeError: list indices must be integers or slices, not str

Why it happens

Almost always: you got a list of dicts back from json.loads(...), a database query, or an API call, and treated the whole list as if it were a single dict.

python
import json
raw = '[{"name": "alice"}, {"name": "bob"}]'
users = json.loads(raw)        # this is a LIST of dicts, not a dict
print(users["name"])           # TypeError

The fix is almost always to index into the list first and then into the dict, or to loop over the list.

How to fix it

Index by position, then by key:

python
users = [{"name": "alice"}, {"name": "bob"}]
print(users[0]["name"])        # alice

Loop when you want every element:

python
for user in users:
    print(user["name"])
+ setup added so this can run · re-runs 2 earlier blocks on this page first
users = [{"name": "alpha"}, {"name": "beta"}, {"name": "gamma"}]

# Earlier blocks on this page that this one builds on, re-run quietly first.
try:
    import json
except Exception:
    pass
_pm_earlier = [
    'import json\nraw = \'[{"name": "alice"}, {"name": "bob"}]\'\nusers = json.loads(raw)        # this is a LIST of dicts, not a dict\nprint(users["name"])           # TypeError',
    'users = [{"name": "alice"}, {"name": "bob"}]\nprint(users[0]["name"])        # alice',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

Use a list comprehension to extract a column:

python
names = [u["name"] for u in users]
+ setup added so this can run · re-runs 2 earlier blocks on this page first
users = [{"name": "alpha"}, {"name": "beta"}, {"name": "gamma"}]

# Earlier blocks on this page that this one builds on, re-run quietly first.
try:
    import json
except Exception:
    pass
_pm_earlier = [
    'import json\nraw = \'[{"name": "alice"}, {"name": "bob"}]\'\nusers = json.loads(raw)        # this is a LIST of dicts, not a dict\nprint(users["name"])           # TypeError',
    'users = [{"name": "alice"}, {"name": "bob"}]\nprint(users[0]["name"])        # alice',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

Or restructure the data into a dict-by-id, if you'll be doing many lookups:

python
users = [{"id": 41, "name": "alice"}, {"id": 42, "name": "bob"}]
users_by_id = {u["id"]: u for u in users}
print(users_by_id[42]["name"])

When you'd see this in real code

  • json.loads(response.text) returned a list, not a dict. You assumed the wrong shape for the API response.
  • You turned a csv.DictReader into a list (rows = list(reader)), which gives a list of dicts, and then wrote rows["column"] instead of looping.
  • An ORM query returned multiple rows, but you treated the whole result set as one row.
  • You stored a list in a variable named like a dict (config = [...]) and later wrote config["host"].

See Also

Found this useful? Share it with a friend who’s learning Python.