Khi nào thì i / i x = khác biệt với khác i


212

Tôi đã nói rằng +=có thể có hiệu ứng khác với ký hiệu tiêu chuẩn i = i +. Có một trường hợp trong đó i += 1sẽ khác với i = i + 1?


7
+=hành động như extend()trong trường hợp danh sách.
Ashwini Chaudhary

12
@AshwiniChaudhary Đó là một sự khác biệt khá tinh tế, xem xét đó i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]True. Nhiều nhà phát triển có thể không nhận thấy rằng id(i)những thay đổi cho một hoạt động, nhưng không thay đổi.
kojiro

1
@kojiro - Mặc dù đó là một sự khác biệt tinh tế, tôi nghĩ đó là một điều quan trọng.
mgilson

@mgilson nó rất quan trọng, và vì vậy tôi cảm thấy cần một lời giải thích. :)
kojiro

1
Câu hỏi liên quan về sự khác biệt giữa hai trong Java: stackoverflow.com/a/7456548/245966
jakub.g

Câu trả lời:


317

Điều này phụ thuộc hoàn toàn vào đối tượng i.

+=gọi __iadd__phương thức (nếu nó tồn tại - quay trở lại __add__nếu nó không tồn tại) trong khi +gọi __add__phương thức 1 hoặc __radd__phương thức trong một vài trường hợp 2 .

Từ phối cảnh API, __iadd__được cho là được sử dụng để sửa đổi các đối tượng có thể thay đổi tại chỗ (trả lại đối tượng đã bị thay đổi) trong khi __add__sẽ trả về một thể hiện mới của một cái gì đó. Đối với các đối tượng không thay đổi , cả hai phương thức đều trả về một thể hiện mới, nhưng __iadd__sẽ đặt thể hiện mới vào không gian tên hiện tại với cùng tên mà thể hiện cũ có. Đây là lý do tại sao

i = 1
i += 1

dường như tăng lên i. Trong thực tế, bạn nhận được một số nguyên mới và gán nó "trên đầu trang" i- mất một tham chiếu đến số nguyên cũ. Trong trường hợp này, i += 1hoàn toàn giống như i = i + 1. Nhưng, với hầu hết các đối tượng có thể thay đổi, đó là một câu chuyện khác:

Như một ví dụ cụ thể:

a = [1, 2, 3]
b = a
b += [1, 2, 3]
print a  #[1, 2, 3, 1, 2, 3]
print b  #[1, 2, 3, 1, 2, 3]

so với:

a = [1, 2, 3]
b = a
b = b + [1, 2, 3]
print a #[1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]

thông báo như thế nào trong ví dụ đầu tiên, kể từ batham khảo cùng một đối tượng, khi tôi sử dụng +=trên b, nó thực sự thay đổi b(và anhìn thấy sự thay đổi đó quá - Sau khi tất cả, nó tham khảo danh sách giống nhau). Tuy nhiên, trong trường hợp thứ hai, khi tôi thực hiện b = b + [1, 2, 3], điều này sẽ đưa danh sách btham chiếu và nối nó với một danh sách mới [1, 2, 3]. Sau đó, nó lưu trữ danh sách được nối trong không gian tên hiện tại dưới dạng b- Không liên quan bđến dòng trước đó là gì .


1 Trong biểu thức x + y, nếu x.__add__không thực hiện hoặc nếu x.__add__(y)lợi nhuận NotImplemented xycó các loại khác nhau , sau đó x + ycố gắng để gọi y.__radd__(x). Vì vậy, trong trường hợp bạn có

foo_instance += bar_instance

nếu Fookhông thực hiện __add__hoặc __iadd__sau đó kết quả ở đây giống như

foo_instance = bar_instance.__radd__(bar_instance, foo_instance)

2 Trong biểu thức foo_instance + bar_instance, bar_instance.__radd__sẽ được thử trước foo_instance.__add__ nếu loại bar_instancelà một lớp con của loại foo_instance(ví dụ issubclass(Bar, Foo)). Các hợp lý cho điều này là bởi vì Barlà trong một ý nghĩa một "cấp cao" đối tượng hơn Foonên Barsẽ nhận được tùy chọn của trọng Foohành vi 's.


18
Vâng, +=gọi __iadd__ nếu nó tồn tại , và quay trở lại để thêm và rebinding khác. Đó là lý do tại sao i = 1; i += 1hoạt động mặc dù không có int.__iadd__. Nhưng khác với nit nhỏ, giải thích tuyệt vời.
hủy bỏ

4
@abarnert - Tôi luôn cho rằng int.__iadd__chỉ cần gọi __add__. Tôi rất vui vì đã học được điều gì đó mới hôm nay :).
mgilson

@abarnert - Tôi cho rằng có lẽ là hoàn chỉnh , x + ycác cuộc gọi y.__radd__(x)nếu x.__add__không tồn tại (hoặc lợi nhuận NotImplementedxylà các loại khác nhau)
mgilson

Nếu bạn thực sự muốn trở thành người hoàn thành, bạn phải đề cập rằng bit "nếu nó tồn tại" đi qua các cơ chế getattr thông thường, ngoại trừ một số quirks với các lớp cổ điển và đối với các loại được triển khai trong API C, thay vào đó, nó sẽ tìm nb_inplace_addhoặc sq_inplace_concat, và các hàm API C đó có các yêu cầu khắt khe hơn các phương thức lừa đảo Python và, Nhưng tôi không nghĩ rằng điều đó có liên quan đến câu trả lời. Sự khác biệt chính là +=cố gắng thực hiện một bổ sung tại chỗ trước khi quay lại hành động như thế +, mà tôi nghĩ bạn đã giải thích.
abarnert

Vâng, tôi cho rằng bạn đúng ... Mặc dù tôi có thể tin vào lập trường rằng API C không phải là một phần của python . Đó là một phần của Cpython :-P
mgilson

67

Dưới vỏ bọc, i += 1làm một cái gì đó như thế này:

try:
    i = i.__iadd__(1)
except AttributeError:
    i = i.__add__(1)

Trong khi i = i + 1làm một cái gì đó như thế này:

i = i.__add__(1)

Đây là một sự đơn giản hóa một chút, nhưng bạn có thể hiểu: Python cung cấp cho các kiểu một cách xử lý +=đặc biệt, bằng cách tạo ra một __iadd__phương thức cũng như một __add__.

Ý định là các loại có thể thay đổi, như list, sẽ tự biến đổi __iadd__(và sau đó quay trở lại self, trừ khi bạn đang làm điều gì đó rất khó khăn), trong khi các loại không thay đổi, như int, sẽ không thực hiện nó.

Ví dụ:

>>> l1 = []
>>> l2 = l1
>>> l1 += [3]
>>> l2
[3]

Bởi vì l2cùng một đối tượng l1, và bạn bị đột biến l1, bạn cũng bị đột biến l2.

Nhưng:

>>> l1 = []
>>> l2 = l1
>>> l1 = l1 + [3]
>>> l2
[]

Ở đây, bạn đã không đột biến l1; thay vào đó, bạn đã tạo một danh sách mới l1 + [3]và bật lại tên l1để trỏ vào nó, để lại l2chỉ vào danh sách ban đầu.

(Trong +=phiên bản, bạn cũng đã rebinding l1, chỉ trong trường hợp đó, bạn đã đảo ngược nó giống như listnó đã bị ràng buộc, vì vậy bạn thường có thể bỏ qua phần đó.)


không __iadd__thực sự gọi __add__trong trường hợp của một AttributeError?
mgilson

Vâng, i.__iadd__không gọi __add__; đó là i += 1cuộc gọi đó __add__.
hủy bỏ

errr ... Vâng, đó là những gì tôi muốn nói. Hấp dẫn. Tôi đã không nhận ra rằng nó đã được thực hiện tự động.
mgilson

3
Nỗ lực đầu tiên thực sự i = i.__iadd__(1)- iadd có thể sửa đổi đối tượng tại chỗ, nhưng không phải như vậy, và do đó dự kiến ​​sẽ trả về kết quả trong cả hai trường hợp.
lvc

Lưu ý rằng phương tiện này mà operator.iaddcác cuộc gọi __add__trên AttributeError, nhưng nó có thể không rebind kết quả ... do đó, i=1; operator.iadd(i, 1)lợi nhuận 2 và lá ithiết lập để 1. Mà hơi khó hiểu.
hủy bỏ

6

Đây là một ví dụ so sánh trực tiếp i += xvới i = i + x:

def foo(x):
  x = x + [42]

def bar(x):
  x += [42]

c = [27]
foo(c); # c is not changed
bar(c); # c is changed to [27, 42]
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.