6.7. View và bản sao

Một view là cửa sổ thứ hai vào cùng khối dữ liệu với nguồn. Không có dữ liệu nào được sao chép; view giữ một bộ mô tả mới (hình dạng, stride và dtype riêng) nhưng chia sẻ bộ đệm. View về cơ bản là miễn phí.

Một bản sao yêu cầu camera cấp một bộ đệm mới và duyệt nguồn để điền vào đó. Bản sao tốn cả thời gian lẫn RAM.

Hầu hết các phương thức định hình lại đều tạo ra view. Hầu hết các phương thức biến đổi dữ liệu đều tạo ra bản sao. Biết cái nào là cái nào sẽ quyết định liệu vòng lặp nóng có làm camera hết RAM hay không.

6.7.1. Reshape

reshape() trả về một mảng có hình dạng yêu cầu. Tổng số phần tử phải không đổi, nếu không ValueError sẽ được phát sinh:

a = np.arange(12, dtype=np.uint8)
m = a.reshape((3, 4))

Kết quả là một view -- ma chia sẻ dữ liệu. Ghi qua m[0, 0] = 99 cũng thay đổi a[0].

Gán một tuple mới cho shape là cách viết tắt của cùng thao tác:

a = np.arange(9)
a.shape = (3, 3)

6.7.2. Transpose

transpose() (hoặc phím tắt .T) đảo ngược các trục. Được triển khai bằng cách đảo ngược các stride -- không có dữ liệu nào bị di chuyển:

m = np.arange(6, dtype=np.uint8).reshape((2, 3))
t = m.T                  # shape (3, 2), shares m's buffer

Một view đã transpose không duyệt khối dữ liệu một cách liên tục. Đọc t ở trên theo từng hàng sẽ truy cập các vị trí bộ nhớ 0, 3, 1, 4, 2, 5, không phải thứ tự 0, 1, 2, 3, 4, 5 mà các byte được bố trí trong đó. Các phép toán số học thông thường và phép rút gọn xử lý điều đó tốt -- chúng bước qua các stride -- nhưng tobytes() thì không thể, vì nó trả về bộ đệm nền trực tiếp mà không sao chép. Các byte trong bộ đệm không khớp với thứ tự mà hình dạng của view ngụ ý, vì vậy phương thức này phát sinh ValueError trên bất kỳ view không liên tục nào. Khi cần các byte theo thứ tự đã transpose, hãy buộc tạo một bản sao liên tục mới trước:

bytes_out = t.copy().tobytes()

6.7.3. Flatten và flat

flatten() trả về một bản sao 1-D của mảng:

f = m.flatten()          # new dense 1-D ndarray

Truyền order='C' (mặc định) để duyệt trục cuối trước hoặc order='F' cho trục đầu tiên trước:

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 là dạng iterator. Nó trả ra từng phần tử của ndarray bất kỳ bậc nào dưới dạng scalar, mà không cấp phát một bản sao phẳng:

for x in m.flat:
    print(x)

Khi ứng dụng cần duyệt qua mọi phần tử, hãy ưu tiên flat; khi cần một bộ đệm 1-D dày đặc để truyền cho hàm khác, hãy dùng flatten().

6.7.4. Lặp

Lặp qua mảng 1-D trả ra scalar; lặp qua mảng bậc cao hơn trả ra view (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])

Các hàng được trả ra khi lặp qua ma trận là view, vì vậy sửa đổi chúng sẽ sửa đổi nguồn.

6.7.5. Bản sao

copy() là cách tường minh để lấy một ndarray độc lập mà các sửa đổi không ảnh hưởng đến bản gốc. Một bộ đệm mới được cấp phát và nguồn được duyệt vào đó:

c = a.copy()

tobytes() trả về một bytearray chia sẻ bộ nhớ với khối dữ liệu của mảng. Ghi qua bytearray sẽ sửa đổi mảng tại chỗ. Phát sinh ValueError nếu mảng không dày đặc (một view đã slice, một transpose, ...).

tolist() trả về nội dung dưới dạng list Python có thể lồng nhau. Hữu ích để tuần tự hóa kết quả nhỏ; tốn kém với kết quả lớn vì mỗi phần tử trở thành một đối tượng Python riêng biệt.

6.7.6. Thao tác nào trả về cái gì

Quy tắc đầy đủ:

Các thao tác sau trả về view:

  • slicing -- a[1:5], a[::2], m[:, 0];

  • chỉ mục đơn trục của mảng bậc cao hơn -- m[0];

  • lặp qua mảng n-D;

  • reshape(), khi bố cục yêu cầu tương thích;

  • transpose() / .T;

  • frombuffer();

  • asarray(), khi dtype khớp.

Các thao tác sau trả về bản sao:

  • copy();

  • flatten();

  • chỉ mục boolean -- a[mask];

  • phép toán số học -- a + b, a * 2, np.sin(a);

  • array() -- luôn sao chép, kể cả từ mảng khác;

  • concatenate().

Chỉ dùng bản sao tường minh khi thực sự cần một bộ đệm độc lập. Trên camera với RAM hạn chế, sự khác biệt giữa view và bản sao thường là ranh giới giữa mã chạy được và mã không chạy được.