Tại sao b + = (4,) hoạt động và b = b + (4,) không hoạt động khi b là danh sách?


77

Nếu chúng ta lấy b = [1,2,3]và nếu chúng ta thử làm:b+=(4,)

Nó trả về b = [1,2,3,4], nhưng nếu chúng ta thử làm b = b + (4,)thì nó không hoạt động.

b = [1,2,3]
b+=(4,) # Prints out b = [1,2,3,4]
b = b + (4,) # Gives an error saying you can't add tuples and lists

Tôi dự kiến ​​sẽ b+=(4,)thất bại khi bạn không thể thêm một danh sách và một tuple, nhưng nó đã hoạt động. Vì vậy, tôi đã cố gắng b = b + (4,)mong đợi để có được kết quả tương tự, nhưng nó đã không hoạt động.


4
Tôi tin rằng một câu trả lời có thể được tìm thấy ở đây .
jochen


Lúc đầu, tôi đã đọc sai và cố gắng đóng nó quá rộng, sau đó rút lại. Sau đó, tôi nghĩ rằng nó phải là một bản sao, nhưng không chỉ tôi không thể bỏ phiếu lại, tôi đã nhổ tóc ra để cố gắng tìm những câu trả lời khác giống như vậy. : /
Karl Knechtel

Câu hỏi rất giống nhau: stackoverflow.com/questions/58048664/ từ
Tweetsash

Câu trả lời:


70

Vấn đề với câu hỏi "tại sao" là thông thường chúng có thể có nghĩa là nhiều thứ khác nhau. Tôi sẽ cố gắng trả lời từng câu tôi nghĩ bạn có thể có trong đầu.

"Tại sao nó có thể hoạt động khác đi?" được trả lời bởi ví dụ này . Về cơ bản, +=cố gắng sử dụng các phương thức khác nhau của đối tượng: __iadd__(chỉ được kiểm tra ở phía bên trái), vs __add____radd__("thêm ngược", kiểm tra ở phía bên phải nếu bên trái không có __add__) cho +.

"Chính xác thì mỗi phiên bản làm gì?" Nói tóm lại, list.__iadd__phương thức này thực hiện tương tự như list.extend(nhưng vì thiết kế ngôn ngữ, vẫn có một bài tập trở lại).

Điều này cũng có nghĩa là ví dụ

>>> a = [1,2,3]
>>> b = a
>>> a += [4] # uses the .extend logic, so it is still the same object
>>> b # therefore a and b are still the same list, and b has the `4` added
[1, 2, 3, 4]
>>> b = b + [5] # makes a new list and assigns back to b
>>> a # so now a is a separate list and does not have the `5`
[1, 2, 3, 4]

+, tất nhiên, tạo một đối tượng mới, nhưng rõ ràng yêu cầu một danh sách khác thay vì cố gắng kéo các phần tử ra khỏi một chuỗi khác.

"Tại sao nó hữu ích cho + = để làm điều này? Nó hiệu quả hơn; extendphương pháp không phải tạo một đối tượng mới. Tất nhiên, điều này đôi khi có một số hiệu ứng đáng ngạc nhiên (như trên), và nói chung Python không thực sự hiệu quả , nhưng những quyết định này đã được đưa ra từ lâu.

"Lý do không cho phép thêm danh sách và bộ dữ liệu bằng + là gì?" Xem tại đây (cảm ơn, @ Splash58); một ý tưởng là (tuple + list) sẽ tạo ra cùng loại với (list + tuple), và không rõ kết quả sẽ là loại nào. +=Không có vấn đề này, vì a += brõ ràng không nên thay đổi loại a.


2
Oof, hoàn toàn đúng. Và danh sách không sử dụng |, vì vậy điều đó làm hỏng ví dụ của tôi. Nếu tôi nghĩ về một ví dụ rõ ràng hơn sau này tôi sẽ trao đổi nó.
Karl Knechtel

1
Btw |cho các bộ là một toán tử đi lại nhưng +cho các danh sách thì không. Vì lý do đó, tôi không nghĩ rằng tranh luận về sự mơ hồ kiểu đặc biệt mạnh mẽ. Vì toán tử không đi lại tại sao yêu cầu giống nhau cho các loại? Người ta chỉ có thể đồng ý rằng kết quả có loại lhs. Mặt khác, bằng cách hạn chế list + iterator, nhà phát triển được khuyến khích rõ ràng hơn về ý định của họ. Nếu bạn muốn tạo một danh sách mới có chứa nội dung được amở rộng bởi nội dung từ bđó thì có một cách để làm điều này : new = a.copy(); new += b. Đó là một dòng nữa nhưng rõ ràng.
a_guest

Lý do tại sao a += bhành xử khác hơn a = a + blà không hiệu quả. Thật ra Guido coi hành vi đã chọn ít gây nhầm lẫn. Tưởng tượng một hàm nhận danh sách alà đối số và sau đó thực hiện a += [1, 2, 3]. Cú pháp này chắc chắn trông giống như nó sửa đổi danh sách tại chỗ, thay vì tạo một danh sách mới, vì vậy quyết định được đưa ra là nó sẽ hành xử theo những gì hầu hết mọi người trực giác về kết quả mong đợi. Tuy nhiên, cơ chế cũng phải làm việc cho các loại bất biến như ints, dẫn đến thiết kế hiện tại.
Sven Marnach

Cá nhân tôi nghĩ rằng việc thiết kế thực sự là nhiều bối rối vì chỉ làm a += bmột cách viết tắt cho a = a + b, như Ruby đã làm, nhưng tôi có thể hiểu làm thế nào chúng tôi đến đó.
Sven Marnach

21

Chúng không tương đương:

b += (4,)

là viết tắt của:

b.extend((4,))

trong khi +nối các danh sách, do đó:

b = b + (4,)

bạn đang cố gắng nối một tuple vào danh sách


14

Khi bạn làm điều này:

b += (4,)

được chuyển đổi thành này:

b.__iadd__((4,)) 

Dưới mui xe nó gọi b.extend((4,)), extendchấp nhận một trình vòng lặp và đây là lý do tại sao điều này cũng hoạt động:

b = [1,2,3]
b += range(2)  # prints [1, 2, 3, 0, 1]

nhưng khi bạn làm điều này:

b = b + (4,)

được chuyển đổi thành này:

b = b.__add__((4,)) 

chỉ chấp nhận danh sách đối tượng.


4

Từ các tài liệu chính thức, cho các loại trình tự có thể thay đổi cả hai:

s += t
s.extend(t)

được định nghĩa là:

mở rộng svới nội dung củat

Điều này khác với việc được định nghĩa là:

s = s + t    # not equivalent in Python!

Điều này cũng có nghĩa là bất kỳ loại trình tự nào sẽ hoạt độngt , bao gồm một bộ dữ liệu như trong ví dụ của bạn.

Nhưng nó cũng hoạt động cho phạm vi và máy phát điện! Chẳng hạn, bạn cũng có thể làm:

s += range(3)

3

Các toán tử gán "tăng cường" như +=đã được giới thiệu trong Python 2.0, được phát hành vào tháng 10 năm 2000. Thiết kế và lý do được mô tả trong PEP 203 . Một trong những mục tiêu được tuyên bố của các nhà khai thác này là sự hỗ trợ của các hoạt động tại chỗ. Viết

a = [1, 2, 3]
a += [4, 5, 6]

được cho là để cập nhật danh sách a tại chỗ . Điều này quan trọng nếu có các tham chiếu khác đến danh sách a, ví dụ: khi ađược nhận làm đối số hàm.

Tuy nhiên, hoạt động không phải lúc nào cũng diễn ra, vì nhiều loại Python, bao gồm số nguyên và chuỗi, là bất biến , vì vậy, ví dụ, i += 1đối với một số nguyên ikhông thể hoạt động tại chỗ.

Tóm lại, các toán tử gán gán tăng cường được cho là hoạt động tại chỗ khi có thể và tạo một đối tượng mới theo cách khác. Để tạo điều kiện cho các mục tiêu thiết kế này, biểu thức x += yđã được chỉ định để hành xử như sau:

  • Nếu x.__iadd__được xác định, x.__iadd__(y)được đánh giá.
  • Mặt khác, nếu x.__add__được thực hiện x.__add__(y)được đánh giá.
  • Mặt khác, nếu y.__radd__được thực hiện y.__radd__(x)được đánh giá.
  • Nếu không sẽ gây ra một lỗi.

Kết quả đầu tiên thu được từ quá trình này sẽ được gán lại x(trừ khi kết quả đó là NotImplementedđơn, trong trường hợp đó, việc tra cứu tiếp tục với bước tiếp theo).

Quá trình này cho phép các loại hỗ trợ sửa đổi tại chỗ để thực hiện __iadd__(). Các loại không hỗ trợ sửa đổi tại chỗ không cần thêm bất kỳ phương thức ma thuật mới nào, vì Python sẽ tự động quay trở lại về cơ bản x = x + y.

Vì vậy, cuối cùng chúng ta hãy đến với câu hỏi thực tế của bạn - tại sao bạn có thể thêm một tuple vào danh sách với toán tử gán tăng. Từ bộ nhớ, lịch sử của điều này đại khái như thế này: list.__iadd__()Phương thức được triển khai để gọi list.extend()phương thức đã có sẵn trong Python 2.0. Khi các trình vòng lặp được giới thiệu trong Python 2.1, list.extend()phương thức đã được cập nhật để chấp nhận các trình vòng lặp tùy ý. Kết quả cuối cùng của những thay đổi này là my_list += my_tuplehoạt động bắt đầu từ Python 2.1. Các list.__add__()phương pháp, tuy nhiên, không bao giờ được cho là để hỗ trợ lặp tùy ý như là đối số bên tay phải - đây được coi là không phù hợp cho một ngôn ngữ mạnh mẽ gõ.

Cá nhân tôi nghĩ rằng việc triển khai các toán tử tăng cường đã kết thúc quá phức tạp trong Python. Nó có nhiều tác dụng phụ đáng ngạc nhiên, ví dụ mã này:

t = ([42], [43])
t[0] += [44]

Dòng thứ hai tăng TypeError: 'tuple' object does not support item assignment, nhưng dù sao thì thao tác vẫn được thực hiện thành công - tsẽ là ([42, 44], [43])sau khi thực hiện dòng gây ra lỗi.


Bravo! Có một tài liệu tham khảo về PEP là đặc biệt hữu ích. Tôi đã thêm một liên kết ở đầu bên kia, vào một câu hỏi SO trước đó về hành vi liệt kê danh sách. Khi tôi nhìn lại Python như thế nào trước 2.3 hoặc lâu hơn, nó dường như không thể sử dụng được so với ngày nay ... (và tôi vẫn có một ký ức mơ hồ về việc thử và không nhận được 1,5 để làm bất cứ điều gì hữu ích trên máy Mac rất cũ)
Karl Knechtel

2

Hầu hết mọi người sẽ mong đợi X + = Y tương đương với X = X + Y. Thật vậy, Tham chiếu bỏ túi Python (phiên bản thứ 4) của Mark Lutz nói trên trang 57 "Hai định dạng sau tương đương nhau: X = X + Y, X + = Y ". Tuy nhiên, những người chỉ định Python không làm cho chúng tương đương. Có thể đó là một lỗi sẽ dẫn đến thời gian gỡ lỗi hàng giờ bởi các lập trình viên nản lòng miễn là Python vẫn được sử dụng, nhưng giờ đây chỉ là cách Python hoạt động. Nếu X là loại trình tự có thể thay đổi, X + = Y tương đương với X.extend (Y) và không phải là X = X + Y.


> Có thể đó là một lỗi sẽ dẫn đến thời gian gỡ lỗi hàng giờ bởi các lập trình viên nản lòng chừng nào Python còn sử dụng <- bạn có thực sự phải chịu đựng điều này không? Bạn dường như đang nói từ kinh nghiệm. Tôi rất muốn nghe câu chuyện của bạn.
Veky

1

Như đã giải thích ở đây , nếu arraykhông thực hiện __iadd__phương thức, thì b+=(4,)nó sẽ chỉ là một cách viết tắt b = b + (4,)nhưng rõ ràng là không, nên phương thức arraythực hiện cũng vậy __iadd__. Rõ ràng việc thực hiện __iadd__phương pháp là như thế này:

def __iadd__(self, x):
    self.extend(x)

Tuy nhiên, chúng tôi biết rằng đoạn mã trên không phải là sự thực thi thực tế của __iadd__phương thức nhưng chúng tôi có thể giả định và chấp nhận rằng có một cái gì đó giống như extendphương thức, chấp nhận tuppleđầu vào.

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.