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
second = first copies the reference in
second, after which
second refers to the same string in memory as
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
and then assign
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…
grid selects the first sublist…
grid 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,
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
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
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.