Predict the output · Python
References, copies and mutability
Two names for one object, copies, and passing values to functions.
Learn: Names, objects and copies in Python7 minute read
In Python every value is an object, and a variable is just a name attached to an object. Most surprising output comes from two names attached to one object: change the object through one name, and the other name sees it. Interviewers ask about this because it causes real test bugs, such as shared test data that one test changes for all the others. These rules let you see when it happens.
Rule 1: assignment attaches a name, it never copies
b = a makes b a second name for the object that a names. This is called binding. A name is bound to an object, and rebinding a name makes it point at a different object. If that object is a list, a change made through either name is visible through both. Nothing is copied unless you ask for a copy.
Rule 2: mutable and immutable objects
Lists, dicts (dictionaries), sets and most objects of your own classes are mutable (they can be changed): methods such as append change them in place, which means the same object is changed and no new one is made. Numbers, strings and tuples (fixed groups of values) are immutable (they cannot be changed): any "change" builds a new object. That is why x += 1 on an int attaches x to a new number, while other names keep the old one.
Rule 3: += on a list is in place, + is not
For a list, a += [1] extends the same list object, so other names see the change. a = a + [2] builds a new list and attaches only a to it. For immutable types both forms build a new object.
Rule 4: functions receive the object itself
A parameter is the name inside the function's definition that receives a value. It becomes a new name for the caller's object. If the function mutates the object, the caller sees it. If the function assigns a new object to the parameter, only the local name moves and the caller sees nothing. People call this "pass by object reference" or "pass by assignment".
def grow(items):
items.append(1)
items = [99]
data = []
grow(data)
grow(data)
print(data)
a = b = [0]
a += [1]
print(b)
a = a + [2]
print(a, b)
[1, 1] [0, 1] [0, 1, 2] [0, 1]
Rule 5: default values are created once
A default argument value is the value a parameter gets when the caller gives none. Defaults are evaluated when the def line (which defines the function) runs, not on each call. A mutable default such as bucket=[] is one shared list that grows across calls. Use None as the default and create the list inside the function.
Rule 6: copies are shallow unless you deep copy
list.copy(), list(a), a[:] and dict.copy() make a new outer container whose items are the same objects. This is a shallow copy: changing an inner list through the copy changes the original as well. copy.deepcopy copies the inner objects too. A related trap: [[0] * 2] * 2 repeats one inner list, so every row is the same object.
import copy nested = [[1], [2]] s = nested.copy() d = copy.deepcopy(nested) s[0].append(9) print(nested, d) grid = [[0] * 2] * 2 grid[0][1] = 7 print(grid)
[[1, 9], [2]] [[1], [2]] [[0, 7], [0, 7]]
Rule 7: is checks identity, == checks value
a is b is true only when both names are attached to the same object. == compares values. CPython, the standard interpreter (the program that runs your Python code), reuses objects for small integers (-5 to 256) and some strings, so is may look like == for small values and then fail for larger ones. Use is only for None, True and False.
A puzzle that combines the rules
A tuple cannot change, but a list inside it can. t[0] += [3] on a tuple holding a list first extends the list in place, then tries to store it back into the tuple and raises TypeError, an error that stops the program unless it is caught. The error appears, yet the list has already changed.
How to answer in an interview
Count the objects and draw an arrow from each name to its object, then follow the arrows line by line. For "is Python pass by value or by reference?", give the direct answer: "Python passes the object itself, often called pass by object reference. The parameter is a new name for the caller's object, so a function can mutate a list it receives, but rebinding the parameter does not affect the caller." Mention the mutable default argument as a common real bug.
Common mistakes
- Expecting
b = ato copy a list. - Treating
a += xanda = a + xas the same for lists. - Using
[]as a default argument value. - Assuming
.copy()copies nested lists. - Comparing numbers with
is.
Recap
Names point at objects, and assignment only adds a name. Mutating an object shows through every name; rebinding moves one name. += mutates lists, defaults are created once, copies are shallow, and is means "same object".
10 questions0/10 solved
| Status | Question | Difficulty | Asked in |
|---|---|---|---|
| Not started | Two names for one list | Easy | |
| Not started | is versus == for lists | Easy | Asked in: Infosys |
| Pro+ | Changing a list inside a function | Easy-Medium | Asked in: TCS |
| Pro+ | Adding to a number inside a function | Easy-Medium | Asked in: TCS |
| Pro+ | a += [2] versus a = a + [3] | Medium | |
| Pro+ | Shallow copy versus deep copy of nested lists | Medium | Asked in: Infosys, TCS |
| Pro+ | Building a grid with [[0] * 2] * 3 | Medium-Hard | |
| Pro+ | is with 256 and 257 | Medium-Hard | |
| Pro+ | A list as a default argument | Medium-Hard | |
| Pro+ | Using += on a list inside a tuple | Hard |
The first 2 questions of each topic are free. Pro+ opens every question.