Sự khác biệt giữa a - = b và a = a - b trong Python


90

Gần đây tôi đã áp dụng giải pháp này để tính trung bình mỗi N hàng của ma trận. Mặc dù giải pháp nói chung hoạt động nhưng tôi đã gặp sự cố khi áp dụng cho mảng 7x1. Tôi đã nhận thấy rằng vấn đề là khi sử dụng -=nhà điều hành. Để làm một ví dụ nhỏ:

import numpy as np

a = np.array([1,2,3])
b = np.copy(a)

a[1:] -= a[:-1]
b[1:] = b[1:] - b[:-1]

print a
print b

kết quả đầu ra:

[1 1 2]
[1 1 1]

Vì vậy, trong trường hợp một mảng a -= btạo ra kết quả khác với a = a - b. Tôi nghĩ cho đến bây giờ rằng hai cách này hoàn toàn giống nhau. Sự khác biệt là gì?

Tại sao phương pháp tôi đang đề cập để tính tổng mọi N hàng trong ma trận lại hoạt động, ví dụ như đối với ma trận 7x4 nhưng không hoạt động đối với mảng 7x1?

Câu trả lời:


80

Lưu ý: sử dụng các thao tác tại chỗ trên mảng NumPy chia sẻ bộ nhớ không còn là vấn đề trong phiên bản 1.13.0 trở đi (xem chi tiết tại đây ). Hai hoạt động sẽ tạo ra cùng một kết quả. Câu trả lời này chỉ áp dụng cho các phiên bản trước đó của NumPy.


Việc thay đổi mảng khi chúng đang được sử dụng trong tính toán có thể dẫn đến kết quả không mong muốn!

Trong ví dụ trong câu hỏi, phép trừ với -=sửa đổi phần tử thứ hai của avà sau đó ngay lập tức sử dụng phần tử thứ hai đã sửa đổi đó trong phép toán trên phần tử thứ ba của a.

Đây là những gì xảy ra với a[1:] -= a[:-1]từng bước:

  • alà mảng có dữ liệu [1, 2, 3].

  • Chúng tôi có hai quan điểm về dữ liệu này: a[1:][2, 3], và a[:-1][1, 2].

  • Phép trừ tại chỗ -=bắt đầu. Phần tử đầu tiên của a[:-1], 1, bị trừ khỏi phần tử đầu tiên của a[1:]. Điều này đã được sửa đổi ađể được [1, 1, 3]. Bây giờ chúng ta có đó a[1:]là chế độ xem dữ liệu [1, 3]a[:-1]là chế độ xem dữ liệu [1, 1](phần tử thứ hai của mảng ađã được thay đổi).

  • a[:-1]bây giờ [1, 1]và NumPy bây giờ phải trừ phần tử thứ hai của nó là 1 (không phải 2 nữa!) từ phần tử thứ hai của a[1:]. Điều này làm cho a[1:]một cái nhìn của các giá trị [1, 2].

  • abây giờ là một mảng với các giá trị [1, 1, 2].

b[1:] = b[1:] - b[:-1]không có vấn đề này bởi vì b[1:] - b[:-1]tạo một mảng mới trước và sau đó gán các giá trị trong mảng này cho b[1:]. Nó không btự sửa đổi trong khi thực hiện phép trừ, vì vậy các lượt xem b[1:]b[:-1]không thay đổi.


Lời khuyên chung là tránh sửa đổi một chế độ xem này với một chế độ xem khác nếu chúng trùng lặp. Điều này bao gồm các toán tử -=, *=v.v. và sử dụng outtham số trong các hàm phổ quát (như np.subtractnp.multiply) để ghi lại vào một trong các mảng.


4
Tôi thích câu trả lời này hơn câu trả lời hiện được chấp nhận. Nó sử dụng ngôn ngữ rất rõ ràng để cho thấy tác dụng của việc sửa đổi các đối tượng có thể thay đổi tại chỗ. Quan trọng hơn, đoạn cuối trực tiếp nhấn mạnh tầm quan trọng của việc sửa đổi tại chỗ đối với các quan điểm chồng chéo, đây sẽ là bài học rút ra từ câu hỏi này.
Nghỉ hưu 43

43

Nội bộ, sự khác biệt là điều này:

a[1:] -= a[:-1]

tương đương với điều này:

a[1:] = a[1:].__isub__(a[:-1])
a.__setitem__(slice(1, None, None), a.__getitem__(slice(1, None, None)).__isub__(a.__getitem__(slice(1, None, None)))

trong khi điều này:

b[1:] = b[1:] - b[:-1]

ánh xạ tới cái này:

b[1:] = b[1:].__sub__(b[:-1])
b.__setitem__(slice(1, None, None), b.__getitem__(slice(1, None, None)).__sub__(b.__getitem__(slice(1, None, None)))

Trong một số trường hợp, __sub__()__isub__()hoạt động theo cách tương tự. Nhưng các đối tượng có thể thay đổi sẽ tự biến đổi và tự trở lại khi sử dụng __isub__(), trong khi chúng phải trả về một đối tượng mới với __sub__().

Việc áp dụng các thao tác cắt lát trên các đối tượng numpy sẽ tạo ra các khung nhìn trên chúng, do đó việc sử dụng chúng sẽ truy cập trực tiếp vào bộ nhớ của đối tượng "gốc".


11

Các tài liệu nói:

Ý tưởng đằng sau phép gán tăng cường trong Python là nó không chỉ là một cách dễ dàng hơn để viết thông lệ lưu trữ kết quả của một phép toán nhị phân trong toán hạng bên trái của nó, mà còn là một cách để toán hạng bên trái được đề cập biết rằng nó sẽ tự hoạt động, thay vì tạo ra một bản sao sửa đổi của chính nó.

Theo quy tắc ngón tay cái, phân số tăng cường ( x-=y) x.__isub__(y), đối với phép toán IN -place NẾU có thể, khi phân số bình thường ( x = x-y) là x=x.__sub__(y). Trên các đối tượng không thể thay đổi như số nguyên, nó tương đương. Nhưng đối với những thứ có thể thay đổi như mảng hoặc danh sách, như trong ví dụ của bạn, chúng có thể là những thứ rất khác nhau.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.