Memory and Objects
Aliasing
Consider the code:
a = Point(0, 0)
b = a
a
and b
refer to the same object.Any changes to a
will show up in b, and vice versa. This is called aliasing. a
and b
are like two nicknames for the same person.
a = Point(0, 0)
b = a
print(a.x) # 0
print(b.x) # 0
a.move_right()
# both a and b were changed!
print(a.x) # 1
print(b.x) # 1
Aliasing isn't always a problem. Sometimes, you want two names for the same object, but be careful. Aliasing is a common source of bugs!
Function parameters
Aliasing will also happen when you pass an object as a parameter to a function. When you pass the object as a parameter, the object that the function operates on is the same.
def move_right_twice(point):
point.move_right()
point.move_right()
a = Point(0, 0)
move_right_twice(a)
print(a.x) # 2
The object a
gets changed, because the point
in the function is the same as a
. This is usually what you want, but you need to be aware of it.
Example: Reflecting a point
Let's write a helper function that gets the reflected version of a point.
We'll reflect across the y axis, flipping the point from left to right. We need a point that is the same as the original but with the x coordinate multiplied by -1, so that points on the left are reflected to the right, and points on the right are reflected to the left.
# the code here is wrong!
def get_reflected_point(point):
point.x *= -1
return point
The get_reflected_point
is not written correctly! The math is correct and it does return a reflected point as we wanted. But it has "side effects" - it modifies the original point. In this case, we don't want aliasing. The place that calls this function will probably not expect the original point to be changed - see what happens:
first_point = (3, 3)
reflected_point = get_reflected_point(a)
print(reflected_point.x) # shows -3, just like we want
# but it also did something we didn't want
print(first_point.x) # this also shows -3. our original was modified.
The solution is that get_reflected_point()
should make a copy first, and change the copy.
Copying
This is a way to avoid aliasing.
import copy
a = Point()
b = copy.deepcopy(a)
Now a and b are completely separate instances.
So, this is a better way to write get_reflected_point
:
def get_reflected_point(point):
import copy
copied_point = copy.deepcopy(point)
copied_point.x *= -1
return copied_point
This time, the program works as expected:
point = (3, 3)
reflected_point = get_reflected_point(point)
print(reflected_point.x) # shows -3, just like we want.
print(point.x) # this shows 3, our original is left intact.