Tuples versus Lists in Python

“Tupleth verthuth lithts?” said the lispy snake

One thing I often ask for in code review is conversion of tuples to lists. For example, imagine we had this Django admin class:

class BookAdmin(ModelAdmin):
    fieldsets = (
        (
            None,
            {
                "fields": (
                    "id",
                    "name",
                    "created_time",
                )
            },
        ),
    )

    readonly_fields = ("created_time",)

I’d prefer the tuples to all be lists, like:

class BookAdmin(ModelAdmin):
    fieldsets = [
        [
            None,
            {
                "fields": [
                    "id",
                    "name",
                    "created_time",
                ]
            },
        ],
    ]

    readonly_fields = ["created_time"]

This is counter to the examples in the Django admin docs. So, why?

Tuples use parentheses, and parentheses have several uses in Python. Therefore it’s easier to make typo mistakes with them.

First, it’s easy to miss a single trailing comma and accidentally create a parenthesized string instead of a tuple. For example, if we missed it in the above code we would have:

readonly_fields = ("created_time")

This is the string "created_time", instead of a tuple containing that string. Woops.

This often works with code you pass the variable to because strings are iterable character-by-character. (Django guards against this in the particular case of ModelAdmin.readonly_fields with its check admin.E034. But in general Python code doesn’t check for strings versus tuples/lists before iterating).

There’s an argument Python shouldn’t have made strings iterable character-by-character. There could instead be an iterator created with something like str.iter(). However, it’s quite a backwards incompatible change to add now.

Second, it’s easy to miss a couple of trailing commas and again accidentally create a parenthesized string. Imagine we missed the end-of line commas in the above sub-element “fields”:

"fields": (
    "id"
    "name"
    "created_time"
)

Now instead of the tuple ("id", "name", "created_time"), we have the string "idnamecreated_time". Woops again!

This too can silently fail because strings are iterable character-by-character.

Third, it’s easy to create tuples accidentally. The following is a string:

x = "Hello,"

And this is a tuple, containing a string:

x = "Hello",

It’s quite hard to spot the difference! Even with syntax highlighting, the comma is a small character so the difference in colour is easily missed.

(There’s a dedicated flake8 plugin, flake8-tuple for banning exactly this construct, considering how harmful it can be.)

Fourth, they provide an extra step for new users of the language. Many languages have only one list-like type, so learning the subtle differences between tuples and lists takes time. Lists work in nearly every case tuples do, so it’s simpler to stick to them.

I do believe there’s a time and place to use tuples over lists though. The main times to do so are:

But for day-to-day application development, I think they tend to cause more problems than they’re worth.

Fin

Thanks for reading this mini-rant. May you write more readable code,

—Adam


Newly updated: my book Boost Your Django DX now covers Django 5.0 and Python 3.12.


Subscribe via RSS, Twitter, Mastodon, or email:

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

Related posts:

Tags: ,