Chapter 10, Basic
Copies and views
Almost every confusing bug with arrays is, at its core, a case of "I changed this and it changed that." Understanding when NumPy copies and when it shares eliminates this class of errors.
Code for this chapter: básico/cap10_copias_views.py
A view shares memory
When you slice an array, NumPy does not copy the numbers: it creates a new object that points to the same memory, with a different start and a different size. That’s why slicing costs almost nothing, even in huge arrays. The consequence is that writing to the slice writes to the original:
import numpy as np
original = np.arange(6)
fatia = original[1:4]
fatia[0] = 99
print(original)
print(np.shares_memory(original, fatia), fatia.base is original)
[ 0 99 2 3 4 5]
True True
The attribute base points to the array that owns the memory. A view has base, and an array that owns its own data has base equal to None.
When you want an independent array
For a true copy, ask explicitly with .copy():
independente = original[1:4].copy()
independente[0] = -1
print(original[1], np.shares_memory(original, independente), independente.base is None)
99 False True
| Operation | Returns | Note |
|---|---|---|
Slice a[1:4] | View | Always |
a.reshape(...) | View, if possible | Copy only if memory does not allow |
a.T, a.transpose() | View | |
a.ravel() | View, if possible | flatten() always copies |
a[mascara], a[[0, 2]] | Copy | |
a + 1, np.sqrt(a) | New array | Arithmetic operations create a new result |
The same object, two names
The assignment b = a does not copy anything: it creates a second name for the same array (the same label model from the Python course). And the compound assignment operators (+=) alter the array in place, while b = b + 10 creates a new one:
a = np.array([1, 2, 3])
b = a
b += 10
print(a)
b = b + 10
print(a, b)
[11 12 13]
[11 12 13] [21 22 23]
The b += 10 also changed the a, because both are the same object. On the other hand, b = b + 10 created a new array and linked it to the name b, and the a remained as it was.
Functions that modify what they receive
An array passed to a function is the same object, and not a copy. If the function alters the array, the caller sees the change. This is useful for saving memory and dangerous when it happens unintentionally:
def zerar_negativos(a):
a[a < 0] = 0
return a
dados = np.array([3, -1, 4, -5])
resultado = zerar_negativos(dados)
print(dados, resultado is dados)
[3 0 4 0] True
The dados originals have been modified, and the resultado is the same object. A version that does not alters the input uses an operation that creates a new result, like np.maximum(a, 0).
`asarray` and `array`
The np.asarray(x) does not copy if x it is already an array, and the np.array(x) copies by default. Functions that accept "anything resembling an array" usually start with np.asarray(entrada), which converts lists without copying what is already an array:
x = np.arange(3)
print(np.asarray(x) is x, np.array(x) is x)
True False
The rule I follow
If I am going to modify an array that came from outside (from another function, from a parameter, from a slice), I do
.copy()first. If I am only going to read, I do not copy. And when a function intentionally alters the argument, its name or documentation states that.
Exercise 1
Normalize without mutating the input
Write normalizar(a), which returns (a - mínimo) / (máximo - mínimo) without altering the received array.