Một bài đăng gần đây trên Reddit đã gây ra một số bình luận, vì vậy tôi muốn làm rõ. Trong Python, các đối tượng có thể băm phải là bất biến và các đối tượng có thể thay đổi không thể băm được. [Với một ngoại lệ. ]
Trước khi thu thập đuốc và chĩa ba, hãy để tôi giải thích một số thông tin cơ bản
Nếu bạn muốn làm cho các lớp của mình có thể băm được, bạn phải tuân theo hai quy tắc được nêu trong
Một đối tượng có thể băm nếu [1] nó có giá trị băm không bao giờ thay đổi trong suốt thời gian tồn tại của nó [nó cần một phương thức _______________4] và có thể được so sánh với các đối tượng khác [nó cần một phương thức _______________5]. [2] Các đối tượng có thể băm so sánh bằng nhau phải có cùng giá trị băm
Trong Python, số nguyên, số float và bool đều là bất biến. Và bởi vì
>>> ip = ImmutablePoint[10, 20] >>> ip.x, ip.y [10, 20] >>> ip.x = 'changed' # x and y are read-only Traceback [most recent call last]: File "stdin", line 1, in module AttributeError: can't set attribute >>> ip2 = ImmutablePoint[10, 20] >>> ip == ip2 True6, nên
>>> ip = ImmutablePoint[10, 20] >>> ip.x, ip.y [10, 20] >>> ip.x = 'changed' # x and y are read-only Traceback [most recent call last]: File "stdin", line 1, in module AttributeError: can't set attribute >>> ip2 = ImmutablePoint[10, 20] >>> ip == ip2 True7
Hãy tạo một lớp Point bất biến, lớp này có các thuộc tính chỉ đọc
>>> ip = ImmutablePoint[10, 20] >>> ip.x, ip.y [10, 20] >>> ip.x = 'changed' # x and y are read-only Traceback [most recent call last]: File "stdin", line 1, in module AttributeError: can't set attribute >>> ip2 = ImmutablePoint[10, 20] >>> ip == ip2 True8 và
>>> ip = ImmutablePoint[10, 20] >>> ip.x, ip.y [10, 20] >>> ip.x = 'changed' # x and y are read-only Traceback [most recent call last]: File "stdin", line 1, in module AttributeError: can't set attribute >>> ip2 = ImmutablePoint[10, 20] >>> ip == ip2 True9 và nó sử dụng lại các giá trị băm cho các bộ dữ liệu_______________
Chúng ta có thể tạo _______________0 đối tượng và chúng là bất biến. chúng tôi không thể thay đổi thuộc tính
>>> ip = ImmutablePoint[10, 20] >>> ip.x, ip.y [10, 20] >>> ip.x = 'changed' # x and y are read-only Traceback [most recent call last]: File "stdin", line 1, in module AttributeError: can't set attribute >>> ip2 = ImmutablePoint[10, 20] >>> ip == ip2 True8 hoặc
>>> ip = ImmutablePoint[10, 20] >>> ip.x, ip.y [10, 20] >>> ip.x = 'changed' # x and y are read-only Traceback [most recent call last]: File "stdin", line 1, in module AttributeError: can't set attribute >>> ip2 = ImmutablePoint[10, 20] >>> ip == ip2 True9 của chúng
Vì chúng có thể băm được nên chúng ta cũng có thể sử dụng các đối tượng này làm khóa trong từ điển
_______________Bây giờ, vì giá trị băm dựa trên giá trị của đối tượng và giá trị băm của đối tượng không bao giờ thay đổi [theo quy tắc từ Thuật ngữ Python], điều này nhất thiết có nghĩa là chỉ các đối tượng bất biến mới có thể được băm. Lưu ý rằng bạn không thể sử dụng danh sách hoặc từ điển có thể thay đổi làm khóa trong từ điển, chúng cũng không trả về bất kỳ thứ gì từ
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'3_______________
Nhưng tại sao nó lại như thế?
Lý do liên quan đến cách băm được sử dụng trong từ điển. Điều này sẽ yêu cầu một số nền tảng CS về cách hoạt động của từ điển/bản đồ băm [nhưng bạn có thể xem bài nói chuyện về PyCon 2010 của Brandon Rhode, The Mighty Dictionary]. Nhưng câu trả lời ngắn gọn là nếu giá trị của đối tượng thay đổi, thì hàm băm cũng phải thay đổi vì hàm băm dựa trên các giá trị. Tuy nhiên, nếu giá trị của đối tượng thay đổi sau khi nó được sử dụng làm khóa trong từ điển, hàm băm sẽ không còn tham chiếu đến nhóm chính xác trong từ điển cho khóa đó. Hãy sử dụng một ví dụ
Đây là một lớp con của kiểu dữ liệu danh sách tích hợp sẵn của Python [có thể thay đổi] mà chúng tôi đã thêm hàm
>>> ip = ImmutablePoint[10, 20] >>> ip.x, ip.y [10, 20] >>> ip.x = 'changed' # x and y are read-only Traceback [most recent call last]: File "stdin", line 1, in module AttributeError: can't set attribute >>> ip2 = ImmutablePoint[10, 20] >>> ip == ip2 True4 vào_______________
Chúng ta có thể tạo một đối tượng danh sách có thể băm và đặt nó vào từ điển
Nó dường như được làm việc. Chúng tôi thậm chí còn tạo một danh sách có thể băm khác có cùng giá trị và dường như nó cũng hoạt động
_______________Bây giờ chúng ta thay đổi
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'6. Đúng như dự đoán, nó tạo ra một
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'7 vì
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'8 không phải là từ khóa trong từ điển, chỉ có
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'9 là [điều này hóa ra là sai, nhưng tạm thời hãy bỏ qua điều đó]_______________2
Nhưng đây là điều.
>>> {[1, 2, 3]: 'hello'} Traceback [most recent call last]: File "stdin", line 1, in module TypeError: unhashable type: 'list' >>> hash[[1, 2, 3]] Traceback [most recent call last]: File "stdin", line 1, in module TypeError: unhashable type: 'list' >>> hash[{1:2, 3:4}] Traceback [most recent call last]: File "stdin", line 1, in module TypeError: unhashable type: 'dict'0 không còn hoạt động như một khóa, mặc dù nó là
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'9_______________
Tại sao lại thế này? . Khi chúng tôi thay đổi
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'6, chúng tôi cũng thay đổi khóa từ điển_______________
Vì vậy, điều này có nghĩa là chìa khóa bây giờ là
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'8, nhưng nó nằm trong thùng/khe dành cho
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'9. Nhưng
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'9 của
>>> {[1, 2, 3]: 'hello'} Traceback [most recent call last]: File "stdin", line 1, in module TypeError: unhashable type: 'list' >>> hash[[1, 2, 3]] Traceback [most recent call last]: File "stdin", line 1, in module TypeError: unhashable type: 'list' >>> hash[{1:2, 3:4}] Traceback [most recent call last]: File "stdin", line 1, in module TypeError: unhashable type: 'dict'0 sẽ không hoạt động vì giá trị của khóa bây giờ là
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'8 và Python chỉ cho rằng đó là một xung đột hàm băm
Để làm cho mọi thứ trở nên tồi tệ hơn, hãy thử đặt
>>> import collections >>> class HashableList[collections.UserList]: .. def __hash__[self]: .. return hash[tuple[self]]0 thành
>>> import collections >>> class HashableList[collections.UserList]: .. def __hash__[self]: .. return hash[tuple[self]]1. Hoàn toàn trùng hợp,
>>> import collections >>> class HashableList[collections.UserList]: .. def __hash__[self]: .. return hash[tuple[self]]2 băm vào cùng một thùng/khe như
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'9. Và các giá trị đều giống nhau [vì key cũng đã thay đổi thành
>>> import collections >>> class HashableList[collections.UserList]: .. def __hash__[self]: .. return hash[tuple[self]]2] nên nó hoạt động hoàn toàn ổn mặc dù nó sẽ gây ra lỗi
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'7
Vì vậy, đây là lý do tại sao việc các mục có thể thay đổi có thể được băm là một Điều Xấu. thay đổi chúng cũng thay đổi khóa trong từ điển. Đôi khi điều này hoạt động mặc dù nó sẽ gây ra
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'7 [khi có xung đột hàm băm do trùng hợp ngẫu nhiên] và những lần khác, nó sẽ gây ra
>>> d = {ip: 'hello'} >>> d[ip] 'hello' >>> d[ip2] 'hello'7 khi nó hoạt động [vì khóa từ điển đã thay đổi khi đối tượng có thể thay đổi đã thay đổi].
NHƯNG HÃY CHỜ, MỘT NGOẠI LỆ LÀ GÌ? . Nghĩa là, nó phải có các triển khai
>>> ip = ImmutablePoint[10, 20] >>> ip.x, ip.y [10, 20] >>> ip.x = 'changed' # x and y are read-only Traceback [most recent call last]: File "stdin", line 1, in module AttributeError: can't set attribute >>> ip2 = ImmutablePoint[10, 20] >>> ip == ip2 True5 và
>>> ip = ImmutablePoint[10, 20] >>> ip.x, ip.y [10, 20] >>> ip.x = 'changed' # x and y are read-only Traceback [most recent call last]: File "stdin", line 1, in module AttributeError: can't set attribute >>> ip2 = ImmutablePoint[10, 20] >>> ip == ip2 True4 sau đây_______________1
Đây cũng là những triển khai mặc định cho các lớp Python, vì vậy chúng ta có thể rút ngắn lớp của mình thành như sau
_______________2Trong trường hợp này, chúng tôi duy trì hai quy tắc cho khả năng băm. Nhưng chúng tôi không thực sự có một lớp hữu ích, bởi vì mọi đối tượng thuộc loại
>>> h = HashableList[[1, 2, 3]] >>> d = {h: 'hello'} >>> d {[1, 2, 3]: 'hello'}0 chỉ bằng chính nó và sẽ luôn là
>>> h = HashableList[[1, 2, 3]] >>> d = {h: 'hello'} >>> d {[1, 2, 3]: 'hello'}1 khi so sánh với bất kỳ đối tượng nào khác, bất kể giá trị của nó là bao nhiêu_______________3
Vì vậy, bạn có thể tuân theo hai quy tắc khả năng băm của Python cho lớp của mình hoặc bạn có thể tạo các đối tượng có thể băm, có thể thay đổi mà không thực sự hoạt động trong từ điển. Ngoại lệ duy nhất khi bạn có thể có một lớp có thể băm, có thể thay đổi là khi hàm băm dựa trên danh tính chứ không phải giá trị, điều này hạn chế nghiêm trọng tính hữu dụng của nó như một khóa từ điển. Điều đó có nghĩa là, một cách hiệu quả, chỉ các đối tượng bất biến mới có thể được băm và các đối tượng có thể thay đổi không thể được băm