Adam Johnson

Home | Blog | Training | Projects | Colophon | Contact

Why does Python log a SyntaxWarning saying "list indices must be integers or slices"?

2020-06-21 Sssssssspam

Take this code, which we want to return a list of two breakfast orders:

def get_orders():
    return [
        ["egg", "spam"]
        ["egg", "bacon", "spam"]
    ]

If we import it in Python 3.8+, we’ll see a warning message on line 3:

>>> import example
/.../example.py:3: SyntaxWarning: list indices must be integers or slices, not tuple; perhaps you missed a comma?
  ["egg", "spam"]

This is a new warning added in Python 3.8. From the release notes:

When a comma is missed in code such as [(10, 20) (30, 40)], the compiler displays a SyntaxWarning with a helpful suggestion. This improves on just having a TypeError indicating that the first tuple was not callable. (Contributed by Serhiy Storchaka in bpo-15248.)

Indeed, if we run the function, it raises a TypeError:

>>> example.get_orders()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/.../example.py", line 3, in get_orders
    ["egg", "spam"]
TypeError: list indices must be integers or slices, not tuple

Python emits the SyntaxWarning at import time because it can predict that the code will raise this TypeError. So why does the error occur?

Some objects in Python are indexable, such as lists. (Indexing is a form of subscripting.) You index a list with the syntax: some_list[index_or_slice]. This will fetch a single element from the list in some_list if you use a number as an index, such as some_list[0], or a range of elements if you use a slice as the index, such as some_list[1:3].

Although it is rarely necessary, you can also spread the indexing syntax over multiple lines, for example:

some_list[
    index_or_slice
]

If this happens within parentheses or square brackets, you can also put the square brackets on a new line entirely:

(
    some_list
    [index_or_slice]
)

This looks odd, and I don’t think it’s ever necessary in Python code, but it’s valid.

Our get_orders() function follows this pattern, unintentionally. We could replace some_list with ["egg", "spam"], and [index_or_slice] with ["egg", "bacon", "spam"]. So the second line, ["egg", "bacon", "spam"], is not interpreted as a second list, but as an index of the first list. The index_or_slice in the index is a tuple of three elements, without parentheses: "egg", "bacon", "spam". Lists only support integers and slices for indexing, so this line raises a TypeError.

The fix is to follow the SyntaxWarning and add a comma after the first list:

def get_orders():
    return [
        ["egg", "spam"],
        ["egg", "bacon", "spam"],
    ]

This comma separates the two lists, so our function can return them in the outer list, as intended.

Note there’s now also a comma after the second order list. You should form a habit to always end each item in a multi-line list with a comma, even when it’s the last one. Then you can never encounter the error.

Other Types

Python raises this SyntaxWarning for more types than just lists. It also raises them for several indexable built-in types:

You can see this on the console, for example with an f-string:

>>> def example():
...     return f"123"[1, 2]
...
<stdin>:2: SyntaxWarning: str indices must be integers or slices, not tuple; perhaps you missed a comma?

Fin

I hope this helps you understand and fix this error,

—Adam


Are your Django project's tests slow? Read Speed Up Your Django Tests now!


Subscribe via RSS, Twitter, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: python