Python’s Default Argument Mutability Trick: A Real Use Case

Python, known for its readability and simplicity, often warns against using mutable default arguments in function definitions due to unexpected side effects. However, there are a few clever scenarios where this apparent caveat can be turned into an advantage. One of them is the common case of flattening a nested list.

A Use Case: How to flatten nested lists

Let’s say you want to flatten a list of lists but the nested lists could include any number of nested lists themselves. Sounds confusing so let’s give an exmample:

nested_list = [[[1, 2], [3], [[4, [5, [6, 7, 8]]]], 9], [10, 11], 12]

There are various approaches to solve this but a seemingly counterintuitive one is using a recursive function with a mutable default argument.
Take a look at the following function:

def flatten(obj, flat_list=[]):
    if isinstance(obj, list):
        for item in obj:
            flatten(item)
    else:
        flat_list.append(obj)
    return flat_list

By leveraging the mutable nature of default arguments, this function recursively traverses arbitrarily nested lists, efficiently flattening them into a single list. The secret lies in the persistent flat_list default argument, preserving its state across recursive calls. While this might defy conventional wisdom, it showcases the beauty of Python’s flexibility when applied judiciously.

A Word of Caution

It’s important to note that while this trick showcases a clever solution, it’s advisable to avoid using it in production code. This is due to the potential for confusion and the lack of restriction to set the default argument solely as an empty list, which could lead to unintended consequences.

This post was meant to show how we can turn what may seem like a “flaw” into a surprisingly useful “feature”.

If you’re interested in exploring a few legitimate methods for flattening lists, you can refer to this post, which served as the inspiration for the trick presented here.