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;

  • frombuffer();

  • asarray(), gdy dtype się zgadza.

Następujące operacje zwracają kopie:

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.