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.