Passing mutables

Author

Clayton Cafiero

Published

2025-01-05

Passing mutables to functions

You’ll recall that as an argument is passed to a function it is assigned to the corresponding formal parameter. This, combined with mutability, can sometimes cause confusion.1

Mutable and immutable arguments to a function

Here’s an example where some confusion often arises.

>>> def foo(lst):
...     lst.append('Waffles')
...
>>> breakfasts = ['Oatmeal', 'Eggs', 'Pancakes']
>>> foo(breakfasts)
>>> breakfasts
['Oatmeal', 'Eggs', 'Pancakes', 'Waffles']

Some interpret this behavior incorrectly, assuming that Python must handle immutable and mutable arguments differently! This is not correct. Python passes mutable and immutable arguments the same way. The argument is assigned to the formal parameter.

The result of this example is only a little different than if we’d done this:

>>> breakfasts = ['Oatmeal', 'Eggs', 'Pancakes']
>>> lst = breakfasts
>>> lst.append('Waffles')
>>> breakfasts
['Oatmeal', 'Eggs', 'Pancakes', 'Waffles']

All we’ve done is give two different names to the same list, by assignment. In the example with the function foo (above) the only difference is that the name lst exists only within the scope of the function. Otherwise, these two examples behave the same.

Notice that there is no “reference” being passed, just an assignment taking place.

Names have scope, values do not

This example draws out another point. To quote Python guru Ned Batchelder:

Names have scope but no type. Values have type but no scope.

What do we mean by that? Well, in the case where we pass the list breakfast to the function foo, we create a new name for breakfast, lst. This name, lst, exists only within the scope of the function, but the value persists. Since we’ve given this list another name, breakfast, which exists outside the scope of the function we can still access this list once the function call has returned, even though the name lst no longer exists.

Here’s another demonstration which may help make this more clear.

>>> def foo(lst):
...     lst.append('Waffles')
...     print(lst)
...
>>> foo(['Oatmeal', 'Eggs', 'Pancakes'])
['Oatmeal', 'Eggs', 'Pancakes', 'Waffles']

However, now we can no longer use the name lst since it exists only within the scope of the function.

>>> lst
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'lst' is not defined. Did you mean: 'list'?

Where did the list go?

When an object no longer has a name that refers to it, Python will destroy the object in a process called garbage collection. We won’t cover garbage collection in detail. Let it suffice to understand that once an object no longer has a name that refers to it, it will be subject to garbage collection, and thus inaccessible.

So in the previous example, where we passed a list literal to the function, the only time the list had a name was during the execution of the function. Again, the formal parameter is lst, and the argument (in this last example) is the literal ['Oatmeal', 'Eggs', 'Pancakes']. The assignment that took place when the function was called was lst = ['Oatmeal', 'Eggs', 'Pancakes']. Then we appended 'Waffles', printed the list, and returned.

Poof! lst is gone.

Copyright © 2023–2025 Clayton Cafiero

No generative AI was used in producing this material. This was written the old-fashioned way.

Footnotes

  1. If you search on the internet you may find sources that say that immutable object are passed by value and mutable objects are passed by reference in Python. This is not correct! Python always passes by assignment—no exceptions. This is different from many other languages (for example, C, C++, Java). If you haven’t heard these terms before, just ignore them.↩︎

Reuse