6.7. Widoki i kopie#
Widok to drugie okno na ten sam blok danych co źródło. Żadne dane nie są kopiowane; widok przechowuje świeży deskryptor (własny kształt, kroki i dtype), ale współdzieli bufor. Widoki są praktycznie darmowe.
Kopia prosi kamerę o nowy bufor i przechodzi przez źródło, wypełniając go. Kopie kosztują zarówno czas, jak i RAM.
Większość metod zmieniających kształt tworzy widoki. Większość metod przekształcających dane tworzy kopie. Wiedza o tym, która jest która, decyduje o tym, czy gorąca pętla wyczerpie RAM kamery.
6.7.1. Reshape#
reshape() zwraca tablicę o żądanym kształcie. Całkowita liczba elementów musi pozostać niezmieniona, w przeciwnym razie zgłaszany jest ValueError
a = np.arange(12, dtype=np.uint8)
m = a.reshape((3, 4))
Wynikiem jest widok – m i a współdzielą dane. Zapis przez m[0, 0] = 99 zmienia również a[0].
Przypisanie nowej krotki do shape jest skrótem dla tej samej operacji:
a = np.arange(9)
a.shape = (3, 3)
6.7.2. Transpose#
transpose() (lub skrót .T) odwraca osie. Zaimplementowane przez odwrócenie kroków – żadne dane nie są przenoszone:
m = np.arange(6, dtype=np.uint8).reshape((2, 3))
t = m.T # shape (3, 2), shares m's buffer
Transponowany widok nie przechodzi przez blok danych w sposób ciągły. Odczyt powyższego t wiersz po wierszu odwiedza pozycje pamięci 0, 3, 1, 4, 2, 5, a nie bazowy porządek 0, 1, 2, 3, 4, 5, w którym bajty są ułożone. Zwykła arytmetyka i redukcje radzą sobie z tym dobrze – przechodzą przez kroki – ale tobytes() nie może, ponieważ oddaje bazowy bufor bezpośrednio bez kopiowania. Bajty przechowywane w buforze nie odpowiadają kolejności, którą sugeruje kształt widoku, więc metoda zgłasza ValueError przy każdym niespójnym widoku. Gdy bajty są potrzebne w kolejności transponowanej, wymuś najpierw świeżą ciągłą kopię:
bytes_out = t.copy().tobytes()
6.7.3. Flatten i flat#
flatten() zwraca jednowymiarową (1-D) kopię tablicy:
f = m.flatten() # new dense 1-D ndarray
Przekaż order='C' (domyślnie), aby przechodzić najpierw przez ostatnią oś, lub order='F', aby najpierw przez pierwszą oś:
m = np.arange(6, dtype=np.uint8).reshape((2, 3))
# m = [[0, 1, 2],
# [3, 4, 5]]
m.flatten() # array([0, 1, 2, 3, 4, 5], dtype=uint8)
m.flatten(order='F') # array([0, 3, 1, 4, 2, 5], dtype=uint8)
flat jest formą iteratora. Zwraca każdy element ndarray o dowolnym rzędzie jako skalary, bez alokowania płaskiej kopii:
for x in m.flat:
print(x)
Gdy aplikacja musi przejść przez każdy element, preferuj flat; gdy potrzebuje gęstego bufora 1-D do przekazania innej funkcji, użyj flatten().
6.7.4. Iteracja#
Iterowanie tablicy 1-D zwraca skalary; iterowanie tablicy o wyższym rzędzie zwraca widoki (n-1)-D:
m = np.array([[0, 1, 2], [3, 4, 5]], dtype=np.uint8)
for row in m:
print(row) # array([0, 1, 2]), array([3, 4, 5])
Wiersze zwracane przez iterowanie macierzy są widokami, więc ich modyfikowanie modyfikuje źródło.
6.7.5. Kopie#
copy() jest jawnym sposobem uzyskania niezależnego ndarray, którego modyfikacje nie wpływają na oryginał. Alokowany jest nowy bufor, a źródło jest do niego przepisywane:
c = a.copy()
tobytes() zwraca bytearray, który współdzieli pamięć z blokiem danych tablicy. Zapisy przez bytearray modyfikują tablicę w miejscu. Zgłasza ValueError, jeśli tablica nie jest gęsta (wycinany widok, transpozycja, …).
tolist() zwraca zawartość jako potencjalnie zagnieżdżoną listę Pythona list. Przydatne do serializacji małych wyników; kosztowne dla dużych, ponieważ każdy element staje się osobnym obiektem Pythona.
6.7.6. Które operacje co zwracają#
Pełna reguła:
Następujące operacje zwracają widoki:
wycinanie –
a[1:5],a[::2],m[:, 0];indeksowanie pojedynczej osi tablicy o wyższym rzędzie –
m[0];iterowanie tablicy n-D;
reshape(), gdy żądany układ jest zgodny;transpose()/.T;asarray(), gdy dtype się zgadza.
Następujące operacje zwracają kopie:
indeksowanie logiczne –
a[mask];arytmetyka –
a + b,a * 2,np.sin(a);array()– zawsze kopiuje, nawet z innej tablicy;
Sięgaj po jawną kopię tylko wtedy, gdy faktycznie potrzebny jest niezależny bufor. Na kamerze z ograniczoną ilością RAM różnica między widokiem a kopią jest często różnicą między kodem, który się mieści, a kodem, który się nie mieści.