Python: Built-In vs Literals
Published: 2023-05-08 18:39:01

new_list = [] 

or 

new_list = list()

🤔???

When it comes to initializing empty lists or dictionaries in Python, you might wonder whether it is better to use the built-in constructors `list()` and `dict()` or their literal equivalents `[]` and `{}`. In this blog post, we will explore the differences between these two approaches and make a case for why you should prefer the latter.

First, let's take a look at the performance of each method. According to benchmarks conducted by Python developer Tim Peters, using the literals `[]` and `{}` is slightly faster than using their constructor counterparts. This is because the literal syntax is built into the language and doesn't require a function call to create an empty container. While the performance difference may be negligible in most cases, it is still a good habit to use the most efficient method when possible.

Another reason to prefer the literal syntax is its readability. When you see `[]` or `{}` in code, you immediately know that an empty list or dictionary is being created. On the other hand, `list()` and `dict()` require you to mentally parse a function call, which can be more difficult to understand at a glance.

But why choose one over the other? After all, both options achieve the same result of creating an empty container. The answer lies in Python's mutable default arguments behavior.

In Python, function arguments are evaluated only once when the function is defined, not every time the function is called. This means that if you use `list()` or `dict()` as a default argument value and modify it inside the function, the changes will persist across all function calls. This is often not what you want, as it can lead to unexpected behavior.

To illustrate this, let's consider a simple example. Suppose you want to write a function that takes a list of numbers and returns a new list containing only the even numbers. Here's one way to implement this:

```
def get_evens(numbers=[]):
    evens = []
    for number in numbers:
        if number % 2 == 0:
            evens.append(number)
    return evens
```

This function seems correct, but there's a subtle bug lurking in the default argument value of `numbers=[]`. If you call the function multiple times without passing a value for `numbers`, you will get unexpected results:

```
>>> get_evens([1, 2, 3, 4])
[2, 4]
>>> get_evens()
[2, 4]
>>> get_evens()
[2, 4, 2, 4]
>>> get_evens()
[2, 4, 2, 4, 2, 4]
```

What's happening here is that the same mutable list object is being used as the default value every time the function is called. When you modify the list inside the function, the changes persist across all function calls.

To fix this, you can use the literal syntax instead of the constructor:

```
def get_evens(numbers=None):
    if numbers is None:
        numbers = []
    evens = []
    for number in numbers:
        if number % 2 == 0:
            evens.append(number)
    return evens
```

By initializing `numbers` to `None` and using the `is` operator to check for `None`, we ensure that a new empty list is created every time the function is called without a value for `numbers`.

In conclusion, using the literal syntax for empty lists and dictionaries in Python is both more efficient and more readable than using the constructor. Additionally, using `[]` and `{}` as default argument values can help you avoid unexpected behavior when modifying mutable objects inside functions. So the next time you need to create an empty container in Python, reach for the literals instead