Python's list constructor: when and how to use it

Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
6 min. read Python 3.8—3.12
Share
Copied to clipboard.
Tags

When should you use Python's built-in list function to create a list? And when should you use square brackets ([...]) to create a new list instead?

The list constructor is one of Python's built-in functions that is, strangely, frequently underused and overused. Let's take a look at when you should use the list constructor and when you shouldn't.

This article is full of my opinions on this matter. Some of these opinions are not widely shared by the Python community, though I (naturally) think they should be.

What does list do?

There are two ways to use the built-in list function (or "callable" if you prefer, as it's technically a class).

The list function accepts a single argument, which must be an iterable.

>>> a_new_list = list(my_iterable)

The list function will loop over the given iterable and make a new list out it.

Like str, int, dict, and many constructor functions in Python, list can also be called without any arguments:

>>> list()
[]

When given no arguments, list returns an empty list.

Don't use list() to create empty lists

Creating an empty list is a very common operation in Python.

In general, I avoid using list() to create a new list.

I recommend using square brackets (the "list literal" syntax) to make a new empty list:

colors = []

I don't recommend using the built-in list function:

colors = list()

Why not?

Well, the Zen of Python says "there should be one, and preferably only one, obvious way to do it". It's most common to see square brackets used to make an empty list, so I see that as "the one obvious way". Typically if there's a special syntax for something, that way becomes "the one obvious way" in Python.

Creating an empty list using the list literal syntax is faster and I'd say it's both more idiomatic and more readable.

The four uses of list

If list isn't usually used to make an empty list, when is it used?

There are 4 use scenarios that I recommend using the built-in list function:

  1. Copying (shallowly) one list to another
  2. Convert a lazy iterable into a list
  3. Turning any iterable into a list
  4. To make a list-creating factory function

Let's take a look at each of these individually.

Shallow copying lists

Using the list function is my preferred way to make a shallow copy of a list.

I prefer this:

new_list = list(old_list)

Over this:

new_list = old_list.copy()

Why?

Two reasons:

  1. That first approach works on any iterable, not just on lists (most iterables don't have a copy method)
  2. When using list(...), it's more obvious that the resulting object is a list instead of some other data structure.

Turning lazy iterables into lists

Many operations in Python return iterators. The list constructor is handy for turning lazy iterables into concrete lists:

lines_with_numbers = list(enumerate(lines))

Unlike iterators, lists won't disappear after being looped over just once.

Note that, in a sense, this is kind of just another flavor of using list for shallow copying: we're copying each item in a lazy iterable into a new list.

Turning any iterable into a list

I love Python's list comprehensions, but they're not always the right tool for the task at hand.

I very much prefer this:

rows = list(csv_reader)

Over this:

rows = [row for row in csv_reader]

Thanks to duck typing, any object that can be looped over with a for loop or with a comprehension can also be looped over with the built-in list constructor: an iterable is an iterable.

I discuss this particular overuse of comprehensions more in this screencast on overusing comprehensions and in this article.

Perhaps more controversially, I also prefer this:

lines = list(my_file)

Over this:

lines = my_file.readlines()

With list(my_file), it's clear that we're looping over a file and storing each line in a new list (assuming you know that looping over a file provides lines). With my_file.readlines(), it's not quite as obvious that we're creating a list of lines. In fact, many new Pythonistas might assume that readlines would return an iterator (many built-in functions return iterators after all).

This use case for list is really just a variation of the last two, but this is an important enough concept that it's worth repeating in 3 different ways!

Using list as a factory function

Those first three reasons to use list are all about taking an old iterable and making a new list out of it. That's by far the most handy reason to use list and I very often see folks forget that list is a great tool for exactly this purpose! That's not the only use of list though.

The list function is also a handy way to make a "factory function" that creates a new empty list.

The defaultdict class from the collections module can be used to create a dictionary with default values, but it requires a sort of factory function: it needs a callable passed into it that will create those default values.

For example, let's say we're making a function that accepts a string and returns a dictionary grouping words by their lengths:

def group_by_lengths(text):
    lengths = {}
    for word in text.split():
        length = len(word)
        if length in lengths:
            lengths[length].append(word)
        else:
            lengths[length] = [word]

Instead of manually defaulting each new key to a single-item list we could make a dictionary-like object where each value defaults to an empty list by using collections.defaultdict with list as its factory function:

from collections import defaultdict

def group_by_lengths(text):
    lengths = defaultdict(list)
    for word in text.split():
        length = len(word)
        lengths[length].append(word)

The object we pass to defaultdict must be a callable that can be called with 0 arguments and will return a default value for any new key. The built-in list function just happens to be one such callable.

Calling list returns an empty list, so every time a missing key is accessed it'll end up with a value of an empty list.

>>> from collections import defaultdict
>>> weekdays = defaultdict(list)
>>> weekdays["Wednesday"].append("February 15, 2023")
>>> weekdays
defaultdict(<class 'list'>, {'Wednesday': ['February 15, 2023']})
>>> weekdays["Tuesday"]
[]

So while I don't recommend calling list() directly to make an empty list, that feature can be quite handy when using list as a factory function.

When merging iterables use * instead of list()

Sometimes you might end up with multiple iterables that you'd like to merge into a single list.

If both of your iterables are lists, you can just concatenate them:

values = list1 + list2

But if either iterable might not be a list, you'd likely end up converting each of them to a list and then concatenate them:

values = list(args) + list(kwargs.values())

This creates 3 lists, 2 of them temporary. We make a list from args, make a list from kwargs.values(), and then concatenate them to make the third list that we then assign values to.

Instead of converting to lists and concatenating, I usually prefer to use the * operator along with the list literal syntax to unpack each iterable into a single new list instead:

values = [*args, *kwargs.values()]

This directly unpacks each iterable into a new list, which avoids creating temporary lists.

I also find [*a, *b] a bit easier to read at a glance than list(a) + list(b), though new Python programmers likely find it less readable to read at a glance so consider your preferred approach cautiously.

You can find more details on that use of * here and general advice on using * and ** in Python here.

Note that I don't recommend using * for converting one iterable to another:

lines = [*my_file]

That does work, but I find this more readable (it's almost certainly more obvious to a new Python programmer):

lines = list(my_file)

The list constructor seems to me to be the "one obvious way" to convert any iterable into a list. That's pretty much it's sole purpose.

The * operator instead is meant for unpacking one iterable into another.

Embrace list, but don't overuse it

Python's built-in list constructor is super handy. It's short and its purpose is pretty clear: take an iterable and turn it into a list.

If I have a generator, a file object, or a generic iterable whose exact type I don't necessarily know and I'd like to convert it into a list, the list function is what I always reach for.

Whether you're shallow copying a list, turning any generic iterable into a list, or you need a factory function that returns an empty list, remember the list constructor! But make sure not to overuse list: Python's list literal syntax is very much preferable when making empty lists.

Concepts Beyond Intro to Python

Intro to Python courses often skip over some fundamental Python concepts.

Sign up below and I'll share ideas new Pythonistas often overlook.