Find us on GitHub

Teaching basic lab skills
for research computing

Python: Aliasing

Python/Aliasing at YouTube

Hello, and welcome to the seventh episode of the Software Carpentry lecture on Python. In this episode, we'll take a break from introducing new features of the language, and talk a bit about what happens when one piece of data has two or more names.

An alias is a a second name for a piece of data.

Programmers create aliases because it's often easier (or more useful) to have a second way to refer to data than to copy it.

If the data in question is immutable—i.e., if it cannot be modified in place—then aliasing doesn't matter…

…because if the data can't change, it doesn't make a difference how many times it's referred to.

But if data can change in place, then aliasing can lead to some hard-to-find bugs.

In Python, aliasing happens whenever one variable's value is assigned to another variable, because variables are just names that store references to values.

For example, if first refers to the string 'isaac'

…then second = first copies the reference in first to second, after which second refers to the same string in memory as first.

This can't cause problems, because as we've already seen…

…whenever we appear to change a string, Python actually creates a new string behind the scenes.

However, this doesn't happen with lists: they can be changed in place.

Let's assign a list containing the string 'isaac' to the variable first

and then assign first to second. The two variables are now referring to the same thing in memory as before.

If we now append another string to the list that first is pointing at…

…the change is also visible when we look at second's value, because it's the same value.

We didn't explicitly modify second—there's nothing in the expression first.append('newton') to indicate that the value of second will change—but the change happens nonetheless.

This is called a side effect, and as we said at the start of this episode, side effects can lead to some hard-to-find bugs.

As an example, let's look at how we might use lists of lists to implement a two-dimensional grid.

A single outer list serves as the "spine" of the structure, while each row of values is stored in a sublist.

If the variable grid refers to the outer list…

…then grid[0] selects the first sublist…

…and grid[0][1] selects the sublist's second element. This lists-of-lists therefore gives us the two-dimensional indexing we want.

Here's some code to create an N×N grid of 1's.

The first statement creates the outer list—the "spine" of the structure.

Each of the N iterations of the main loop adds another row to this.

Inside the outer loop, another loop creates a sublist of N 1's to be appended to the outer list.

Here's the same code with a small optimization:

instead of temporarily storing the sublist being created in a variable called temp, this code just appends an empty sublist to the outer spine and then starts filling it in place, using grid[-1] to refer to it.

Here's another version that looks almost the same, but which contains a bug due to aliasing.

If we highlight the changes, you can see that the buggy code is just giving the empty list a name.

How can this be a bug? Aren't meaningful variable names supposed to be a good thing?

To see what's going on, let's watch this code execute. At the start of the outer loop, grid and EMPTY both refer to empty lists.

The first thing the loop does is append the list pointed to by EMPTY to the outer list.

When the inner loop runs for the first time…

…it appends a 1 to that inner list.

After two more iterations, our structure looks like this.

Assuming N is 3, the program now goes back around the outer loop, and appends EMPTY to grid again.

Whoops: we've just created an alias for our sublist of three 1's, rather than appending a new empty list for filling in.

The root of the problem is that empty square brackets always means a new empty list.

But if we assign one variable's value to another variable, we're telling Python to create an alias for whatever the first variable was pointing at.

Let's try that again without the variable EMPTY.

We append a new empty list…

…then fill that sublist with 1s.

On the second pass through the outer loop, we append a new empty sublist…

…which we can then start filling. This time, everything is as we want it.

So if aliasing can cause bugs, why does Python allow it?

The first answer is that some languages don't…

…or at least go to great lengths to make it appear as if they don't.

Python, like C++ and Java, does allow aliasing because having multiple references to a million-element list is a lot more efficient than copying it over and over again.

And sometimes we really do want to update structures in place via different routes.

We'll see examples of that in the next episode.