Passing Arguments to Functions
A reference is the association between a variable name and an object. When you assign b = a, both variables refer to the same object—b is an alias for a.
One List, Two Names
Suppose a program gives a second variable the value of an existing list by writing b = a. It is easy to imagine that Python creates a second list. In fact, both names refer to one underlying object. The second name, b, is an alias for a. This distinction becomes especially important when the object is passed into a function and the function changes the object.
A reference is the association between a variable name and an object. Aliasing occurs when more than one variable name refers to the same object.
Tracing a List Mutation
What do you think happens?
If a and b refer to the same list and the first item is changed through b, what will a show?
Reveal answer
Answer: a shows the changed first item
The two names refer to one mutable list. Changing an item changes that one list object, so the change is visible through every alias.
Changing Through an Alias
A list is assigned to a, and b is then assigned from a. The first item is modified through b. Predict the list seen through a.
Create the list: a refers to one list containing 1, 2, and 3.
Create the alias: Writing b = a makes b refer to the same list. It does not create an independent list.
Mutate through b: Changing b[0] changes the first item of the single list object.
Inspect a: Because a still refers to that same list, it also shows the changed first item.
The list seen through a has the changed first item because a and b are aliases for one list.
The important point is that the operation changes the list object, not merely the name used to reach it. Since there is only one underlying list, every alias reveals the updated contents. This is correct Python behavior, although it can be surprising when a programmer expected b = a to make a copy.
What Function Parameters Receive
When an object is passed to a function, the function parameter becomes another variable name associated with that object. If the argument is a list and the function mutates that list, the caller can observe the mutation through its original variable name. The parameter and the caller's variable can therefore act as aliases while the function is working with the object.
To reason about a function call, ask two questions: which object does the argument name refer to, and does the function mutate that object? If the answer to both questions involves the same mutable list, the caller's list can change as a result of the call. The function parameter is another route to the list, not automatically an independent list.
Passing a list to a function does not by itself create an independent list. A function that modifies the received list can change the object that the caller also refers to.
Lists and Strings Behave Differently
Aliasing is most troublesome with mutable objects. Lists are mutable, so their contents can be changed in place. If two names refer to the same list, a modification through one name is visible through the other. Strings are immutable, so their contents cannot be changed in place. An operation that appears to modify a string creates a new string instead.
| Object type | Can contents change in place? | Effect of an apparent change through one name |
|---|---|---|
| List | Yes | All aliases see the changed list |
| String | No | A new string is created and the name can refer to it; another name still refers to the original string |
Why String Aliasing Is Harmless Here
Two names refer to the string banana. The name b is then assigned the result of converting its string to uppercase. What does a refer to afterward?
Start with one string: Both a and b refer to the string banana.
Evaluate the uppercase operation: The operation does not modify banana in place because strings are immutable. It creates the string BANANA.
Rebind b: b is assigned the new string, while a continues to refer to banana.
a still refers to banana, and b refers to BANANA.
Preventing Shared-List Bugs
The safest approach is not to avoid assignment altogether. Instead, be intentional about whether two names should share one mutable list. If two independent lists are required, create an explicit copy rather than assigning the original list directly.
Two list-copying strategies given in the source are slicing, as in new_list = old_list[:], and the list constructor. Either strategy creates an independent list when that is what the program requires. The key debugging question is whether the second variable should share future mutations or should preserve its own contents.
Mistakes with Aliases
Assuming b = a copies a list.
Direct assignment creates another reference to the object; it does not create an independent list.
Fix:
Use slicing or the list constructor when independent lists are needed.Expecting a list mutation to affect only the variable used for the mutation.
There is only one underlying mutable object.
Fix:
Trace the object, not just the variable name, and check whether the names are aliases.Treating a string operation as if it mutated the original string.
Strings are immutable and cannot be changed in place.
Fix:
Keep track of which name is rebound and which name still refers to the original string.Avoiding all assignment instead of deciding intentionally whether sharing is wanted.
Aliasing is not inherently a Python error; it is a source of unexpected behavior when shared mutable state was not intended.
Fix:
Use direct assignment for intentional sharing and an explicit copy for independent list data.
Practice the Trace
A caller has a list named scores. A second name, backup, is assigned from scores. A function receives backup and changes the first item of the received list. Predict whether scores shows that changed first item. Then decide whether the result would differ if backup had been created with an explicit list copy.
Hints
- First determine whether scores and backup refer to one list or two.
- Next determine whether the function changes the list object or creates a separate object.
- Use the list-copying strategies described in this article when independent data is required.
- To solve an aliasing trace, follow the object references in order. Identify the original object, note every variable or parameter that refers to it, and then determine whether an operation mutates that object or rebinds one name to a new object.
Key Takeaways
- Aliasing means that multiple variable names refer to the same object.
- With a mutable list, a mutation through one alias is visible through every other alias, including a function parameter that refers to the list.
- Strings are immutable, so an apparent change creates a new string instead of modifying the original string.
- Use the is operator to check whether two names refer to the same object.
- Use slicing or the list constructor to create an independent list when shared mutations are not intended.