name = 'Brian' # Stores the string 'Brian' and assigns its reference to 'name'
id(name), name # Get the identifier for this reference.(140367933777808, 'Brian')
The value of a variable is stored in the computer’s random access memory, or RAM. Think of it as a huge, contiguous list of data. When we assign a variable name = 'Brian', the string Brian is stored somewhere in this huge list to which the variable name refers. The python function id() returns an identifier for this reference:
name = 'Brian' # Stores the string 'Brian' and assigns its reference to 'name'
id(name), name # Get the identifier for this reference.(140367933777808, 'Brian')
When we assign one variable to another, Python does not create a copy. Instead, both variables refer to the same object in memory. This design gives Python the efficiency of not copying objects unnecessarily.
same_name = name # Assign the same reference to 'same_name'
id(same_name), same_name # Note how the reference identifier is the same!(140367933777808, 'Brian')
The operator is is used for comparing references. It can be used to check if two variables truly refer to the same object:
a = [1, 2, 3]
b = [1, 2, 3]
if a == b: # Compare values
print('Lists a and b have the same values.')
if a is not b: # Compare references
print('The lists refer to different objects!')Lists a and b have the same values.
The lists refer to different objects!
a = [1, 2, 3]
b = a
b[0] = 42 # Modify the list to which b refers.
print(a) # a and b refer to the same list!
if a is b: # Compare references
print('The lists refer to the same object!')[42, 2, 3]
The lists refer to the same object!
The above applies to all Python objects, including Numpy arrays. Therefore, if we want to create an independent copy of a Numpy array so that we can modify the data without affecting the original, we must explicitly create a copy. This can be done with np.copy() or the .copy() function of the array itself:
import numpy as np
identity = np.eye(3, 3) # Create a 3×3 identity matrix.
# 'm' refers to the same object as 'identity', so changing 'm' will change 'identity':
m = identity
m *= 2
print(identity) # 'identity' is changed![[2. 0. 0.]
[0. 2. 0.]
[0. 0. 2.]]
identity = np.eye(3, 3)
# Explicitly copy 'identity'. Now 'm' refers to a copy of the values in 'identity':
m = identity.copy()
m *= 2
print(identity) # 'identity' is not changed.[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
Note that even when we slice an array, the resulting array can still refer to the same underlying data as the original array! In fact, many NumPy operations can return a different view of an array without creating a copy of its data. Changes made through such a view may therefore also affect the original array.
In case you’re in doubt what the default behavior for a particular Numpy function is, then just check the Numpy documentation to be sure. The following frequently used Numpy operations all return a view on the same underlying data:
original = np.array([[1, 2, 3], [4, 5, 6]]) # Create a 2×3 matrix.
print(original)
sliced = original[:, :1] # Get the first column
transposed = original.transpose() # Transpose (swap axes)
flipped = np.flip(original, axis=1) # Mirror the second axis (horizontal)
flattened = original.ravel() # Reshape from 2×3 matrix to a 'flat' array.
reshaped = original.reshape(3, 2) # Reshape from 2×3 to a 3×2 matrix[[1 2 3]
[4 5 6]]
Now, note that if we change a value in the original array, for example by setting the first element to 99, that this also affects the views returned by the slicing, transposing, mirroring, flattening, and reshaping operations:
original[0, 0] = 99 # Change the first element in the original matrix.
print(f'original:\n {original}\n'
f'sliced:\n {sliced}\n'
f'transposed:\n {transposed}\n'
f'flipped:\n {flipped}\n'
f'flattened:\n {flattened}\n'
f'reshaped:\n {reshaped}\n')original:
[[99 2 3]
[ 4 5 6]]
sliced:
[[99]
[ 4]]
transposed:
[[99 4]
[ 2 5]
[ 3 6]]
flipped:
[[ 3 2 99]
[ 6 5 4]]
flattened:
[99 2 3 4 5 6]
reshaped:
[[99 2]
[ 3 4]
[ 5 6]]