Có thể thay đổi, không thay đổi


81

Từ một câu hỏi SO gần đây (xem Tạo từ điển trong python được lập chỉ mục bởi danh sách ), tôi nhận ra rằng tôi có thể đã quan niệm sai về ý nghĩa của các đối tượng có thể băm và bất biến trong python.

  • Trong thực tế, hashable có nghĩa là gì?
  • Mối quan hệ giữa hashable và immmutable là gì?
  • Có các đối tượng có thể thay đổi là các đối tượng có thể băm hoặc không thể thay đổi không thể băm không?

Câu trả lời:


85

Hashing là quá trình chuyển đổi một số lượng lớn dữ liệu thành một lượng nhỏ hơn nhiều (thường là một số nguyên duy nhất) theo cách có thể lặp lại để có thể tra cứu trong bảng theo thời gian không đổi ( O(1)), điều này rất quan trọng để có hiệu suất cao thuật toán và cấu trúc dữ liệu.

Tính bất biến là ý tưởng rằng một đối tượng sẽ không thay đổi theo một cách quan trọng nào đó sau khi nó được tạo ra, đặc biệt là theo bất kỳ cách nào có thể thay đổi giá trị băm của đối tượng đó.

Hai ý tưởng có liên quan với nhau vì các đối tượng được sử dụng làm khóa băm thường phải là bất biến để giá trị băm của chúng không thay đổi. Nếu nó được phép thay đổi thì vị trí của đối tượng đó trong cấu trúc dữ liệu chẳng hạn như bảng băm sẽ thay đổi và sau đó toàn bộ mục đích của việc băm cho hiệu quả sẽ bị đánh bại.

Để thực sự nắm bắt được ý tưởng, bạn nên cố gắng triển khai bảng băm của riêng mình bằng một ngôn ngữ như C / C ++ hoặc đọc phần triển khai Java của HashMaplớp.


1
Hơn nữa, bảng băm không thể phát hiện khi nào hàm băm của các khóa của chúng thay đổi (ít nhất là theo bất kỳ cách nào hiệu quả). Đó là một cạm bẫy phổ biến, ví dụ như trong Java, HashMapkhi một đối tượng bị hỏng nếu bạn sửa đổi một đối tượng được sử dụng làm khóa trong đó: không thể tìm thấy khóa cũ và mới, mặc dù nếu bạn in bản đồ, nó có thể được nhìn thấy ở đó.
doublep

1
Hashable và Immutable có phần liên quan nhưng không giống nhau. Ví dụ: Các phiên bản được tạo từ các lớp tùy chỉnh kế thừa objectcó thể băm nhưng không thể thay đổi. Những trường hợp này có thể được sử dụng có các khóa của một mệnh đề, nhưng chúng vẫn có thể được sửa đổi nếu được chuyển xung quanh.
Pranjal Mittal

13
  • Có các đối tượng có thể thay đổi là các đối tượng có thể băm hoặc không thể thay đổi không thể băm không?

Trong Python, tuple là bất biến, nhưng nó chỉ có thể băm nếu tất cả các phần tử của nó đều có thể băm được.

>>> tt = (1, 2, (30, 40))
>>> hash(tt)
8027212646858338501
>>> tl = (1, 2, [30, 40])
>>> hash(tl)
TypeError: unhashable type: 'list'

Các loại có thể băm

  • Các kiểu nguyên tử không thay đổi đều có thể băm, chẳng hạn như str, byte, kiểu số
  • Một tập hợp cố định luôn có thể băm (các phần tử của nó phải có thể băm theo định nghĩa)
  • Một tuple chỉ có thể băm nếu tất cả các phần tử của nó đều có thể băm được
  • Các loại do người dùng xác định có thể được băm theo mặc định vì giá trị băm của chúng là id ()

8

Từ Bảng chú giải thuật ngữ Python :

Một đối tượng có thể được băm nếu 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 __hash__()phương thức) và có thể được so sánh với các đối tượng khác (nó cần một __eq__()hoặc __cmp__()phương thức). Các đối tượng có thể băm được so sánh bằng nhau phải có cùng giá trị băm.

Tính năng hashability làm cho một đối tượng có thể sử dụng được như một khóa từ điển và một thành viên tập hợp, vì các cấu trúc dữ liệu này sử dụng giá trị băm bên trong.

Tất cả các đối tượng tích hợp bất biến của Python đều có thể băm, trong khi không có vùng chứa có thể thay đổi (chẳng hạn như danh sách hoặc từ điển). Các đối tượng là thể hiện của các lớp do người dùng định nghĩa có thể được băm theo mặc định; tất cả chúng đều so sánh không bằng nhau và giá trị băm của chúng là id () của chúng.

Các phái và bộ phải sử dụng hàm băm để tra cứu hiệu quả trong bảng băm; các giá trị băm phải là không thay đổi, bởi vì việc thay đổi hàm băm sẽ làm xáo trộn cấu trúc dữ liệu và gây ra lỗi dict hoặc tập hợp. Cách dễ nhất để làm cho giá trị băm trở nên bất biến là làm cho toàn bộ đối tượng trở nên bất biến, đó là lý do tại sao cả hai thường được đề cập cùng nhau.

Mặc dù không có đối tượng nào có thể thay đổi được tích hợp sẵn có thể băm, nhưng có thể tạo một đối tượng có thể thay đổi với giá trị băm không thể thay đổi. Thông thường chỉ một phần của đối tượng thể hiện danh tính của nó, trong khi phần còn lại của đối tượng chứa các thuộc tính có thể tự do thay đổi. Miễn là giá trị băm và các hàm so sánh dựa trên danh tính chứ không phải thuộc tính có thể thay đổi và danh tính không bao giờ thay đổi, bạn đã đáp ứng các yêu cầu.


@Andrey: frozensets có thể băm, bộ thì không; cả hai chỉ có thể chứa các mục có thể băm. Ở những nơi mà Mark đã đề cập đến bộ phim, anh ấy đã chính xác, vì vậy tôi không nghĩ rằng anh ấy có ý định nói về đám đông.
tzot

12
Các lớp do người dùng định nghĩa theo mặc định xác định các kiểu có thể băm (băm chỉ là của đối tượng id). Điều này không thể thay đổi trong suốt thời gian tồn tại của đối tượng, vì vậy nó có thể băm được, nhưng điều đó không có nghĩa là bạn không thể xác định các loại có thể thay đổi! Xin lỗi, nhưng hashability không có nghĩa là bất biến.
Scott Griffiths

1
@ScottGriffiths Tôi không biết tại sao tôi phải mất 6 năm để xem nhận xét của bạn, nhưng muộn còn hơn không. Tôi không biết làm thế nào tôi có thể đi xa đến vậy, vì tôi đã than thở về việc không thể đưa các đối tượng có thể thay đổi vào một bộ C ++. Tôi hy vọng bản chỉnh sửa của tôi sẽ sửa chữa mọi thứ.
Mark Ransom

7

Về mặt kỹ thuật, có thể băm có nghĩa là lớp xác định __hash__(). Theo tài liệu:

__hash__()sẽ trả về một số nguyên. Thuộc tính bắt buộc duy nhất là các đối tượng so sánh bằng nhau có cùng giá trị băm; nên kết hợp bằng cách nào đó với nhau (ví dụ như sử dụng riêng hoặc) các giá trị băm cho các thành phần của đối tượng cũng đóng một phần trong việc so sánh các đối tượng.

Tôi nghĩ rằng đối với các loại nội trang Python, tất cả các loại có thể băm cũng là bất biến.

Sẽ rất khó nhưng có lẽ không phải là không thể có một đối tượng có thể thay đổi được mà vẫn được định nghĩa __hash__().


1
Cần lưu ý rằng nó __hash__được định nghĩa theo mặc định để trả về đối tượng id; bạn phải cố gắng thiết lập __hash__ = Noneđể làm cho nó không thể truy cập được. Ngoài ra, như Mark Ransom đã đề cập, có một điều kiện bổ sung là nó chỉ có thể băm nếu giá trị băm không bao giờ thay đổi!
Scott Griffiths

5
Tôi nghĩ rằng câu trả lời là một chút sai lầm, được listđịnh nghĩa __hash__theo nghĩa hasattr([1,2,3], "__hash__")trả về True, tuy nhiên việc gọi hash([1,2,3])làm tăng một TypeError(Python 3), vì vậy nó không chính xác có thể băm. Dựa vào sự tồn tại của __hash__không đủ để xác định xem một cái gì đó có phải là a) có thể băm b) không thể thay đổi
Matti Lyra

4

Có một mối quan hệ tiềm ẩn ngay cả khi không có mối quan hệ rõ ràng buộc giữa không thể thay đổi và có thể băm do tác động qua lại giữa

  1. Các đối tượng có thể băm được so sánh bằng nhau phải có cùng giá trị băm
  2. Một đối tượng có thể được băm nếu 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ó.

Không có vấn đề gì ở đây trừ khi bạn xác định lại __eq__để lớp đối tượng xác định sự tương đương về giá trị.

Khi bạn đã làm xong, bạn cần tìm một hàm băm ổn định luôn trả về cùng một giá trị cho các đối tượng đại diện cho cùng một giá trị (ví dụ: ở đâu __eq__) trả về True và không bao giờ thay đổi trong suốt thời gian tồn tại của một đối tượng.

Thật khó để thấy một ứng dụng có thể thực hiện được điều này, hãy xem xét một loại A có thể đáp ứng các yêu cầu này. Mặc dù có trường hợp suy biến rõ ràng trong đó __hash__trả về một hằng số.

Hiện nay:-

>>> a = A(1)
>>> b = A(1)
>>> c = A(2)
>>> a == b
True
>>> a == c
False
>>> hash(a) == hash(b)
True
>>> a.set_value(c)
>>> a == c
True
>>> assert(hash(a) == hash(c)) # Because a == c => hash(a) == hash(c)
>>> assert(hash(a) == hash(b)) # Because hash(a) and hash(b) have compared equal 
                                 before and the result must stay static over the objects lifetime.

Trên thực tế, điều này có nghĩa là lúc tạo hash (b) == hash (c), mặc dù thực tế là không bao giờ được so sánh bằng. Tôi vẫn cố gắng xem cách nào để xác định hữu ích __hash__() cho một đối tượng có thể thay đổi được định nghĩa so sánh theo giá trị.

Lưu ý : __lt__, __le__, __gt____ge__comparsions không bị ảnh hưởng, do đó bạn vẫn có thể xác định một trật tự của các đối tượng hashable, có thể thay đổi hoặc dựa trên giá trị của họ.


3

chỉ vì đây là thành công hàng đầu của Google, đây là một cách đơn giản để tạo một đối tượng có thể thay đổi có thể băm được:

>>> class HashableList(list):
...  instancenumber = 0  # class variable
...  def __init__(self, initial = []):
...   super(HashableList, self).__init__(initial)
...   self.hashvalue = HashableList.instancenumber
...   HashableList.instancenumber += 1
...  def __hash__(self):
...   return self.hashvalue
... 
>>> l = [1,2,3]
>>> m = HashableList(l)
>>> n = HashableList([1,2,3])
>>> m == n
True
>>> a={m:1, n:2}
>>> a[l] = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> m.hashvalue, n.hashvalue
(0, 1)

Tôi thực sự đã tìm thấy một cách sử dụng cho một cái gì đó như thế này khi tạo một lớp để truyền các bản ghi SQLAlchemy thành một thứ gì đó có thể thay đổi và hữu ích hơn đối với tôi, trong khi vẫn duy trì khả năng băm của chúng để sử dụng làm khóa dict.


3

Bất biến có nghĩa là đối tượng đó sẽ không thay đổi theo bất kỳ cách nào đáng kể trong suốt thời gian tồn tại của nó. Đó là một ý tưởng mơ hồ nhưng phổ biến trong các ngôn ngữ lập trình.

Hashability hơi khác một chút và đề cập đến sự so sánh.

hashable đối tượng An là hashable nếu nó có một giá trị băm mà không bao giờ thay đổi trong suốt cuộc đời của nó (nó cần một__hash__()phương pháp), và có thể được so sánh với các đối tượng khác (nó cần một__eq__()hoặc__cmp__()phương pháp). Các đối tượng có thể băm được so sánh bằng nhau phải có cùng giá trị băm.

Tất cả các lớp do người dùng định nghĩa đều có __hash__phương thức, theo mặc định chỉ trả về ID đối tượng. Vì vậy, một đối tượng đáp ứng các tiêu chí về khả năng băm không nhất thiết là bất biến.

Các đối tượng của bất kỳ lớp mới nào bạn khai báo đều có thể được sử dụng làm khóa từ điển, trừ khi bạn ngăn chặn nó, chẳng hạn như ném từ __hash__

Chúng ta có thể nói rằng tất cả các đối tượng không thay đổi đều có thể được băm, bởi vì nếu hàm băm thay đổi trong thời gian tồn tại của đối tượng, thì điều đó có nghĩa là đối tượng đó đã bị đột biến.

Nhưng không hoàn toàn. Hãy xem xét một bộ tuple có một danh sách (có thể thay đổi). Một số người nói rằng tuple là bất biến, nhưng đồng thời nó cũng không thể băm (ném).

d = dict()
d[ (0,0) ] = 1    #perfectly fine
d[ (0,[0]) ] = 1  #throws

Tính có thể thay đổi và tính bất biến đề cập đến tính cài đặt đối tượng, không phải kiểu. Ví dụ, một đối tượng kiểu tuple có thể được băm hoặc không.


1
"Các đối tượng có thể băm được so sánh bằng nhau phải có cùng giá trị băm." Tại sao? Tôi có thể tạo các đối tượng so sánh bằng nhau nhưng không có cùng giá trị băm.
endolith

1
Có thể tạo các đối tượng như vậy, nhưng sẽ vi phạm khái niệm được định nghĩa trong tài liệu Python. Ý tưởng là, trên thực tế, chúng ta có thể sử dụng yêu cầu này để suy ra hàm ý (tương đương về mặt logic) như vậy: Nếu các hàm băm không bằng nhau, thì các đối tượng không bằng nhau. Rất hữu dụng. Nhiều triển khai, vùng chứa và thuật toán dựa vào hàm ý này để tăng tốc mọi thứ.
user2622016

Trường hợp phổ biến nơi comparison != identityđược khi so sánh giá trị "không hợp lệ" với nhau như float("nan") == float("nan"), hoặc các chuỗi thực tập nội trú từ lát: "apple" is "apple"vs"apple" is "crabapple"[4:]
sleblanc

1

Trong Python, chúng hầu như có thể hoán đổi cho nhau; vì hàm băm được coi là đại diện cho nội dung, vì vậy nó cũng có thể thay đổi được như đối tượng và việc một đối tượng thay đổi giá trị băm sẽ khiến nó không thể sử dụng được như một khóa dict.

Trong các ngôn ngữ khác, giá trị băm liên quan nhiều hơn đến 'danh tính' của đối tượng, và không (nhất thiết) với giá trị. Do đó, đối với một đối tượng có thể thay đổi, con trỏ có thể được sử dụng để bắt đầu băm. Tất nhiên, giả sử rằng một đối tượng không di chuyển trong bộ nhớ (như một số GC làm). Đây là cách tiếp cận được sử dụng trong Lua, chẳng hạn. Điều này làm cho một đối tượng có thể thay đổi được sử dụng như một khóa bảng; nhưng tạo ra một số bất ngờ (khó chịu) cho người mới.

Cuối cùng, việc có kiểu chuỗi bất biến (bộ giá trị) làm cho 'khóa đa giá trị' đẹp hơn.


3
@javier: 'Trong Python, chúng hầu như có thể hoán đổi cho nhau' Nghi ngờ của tôi đề cập đến phần nhỏ không được bao gồm trong 'chủ yếu'
joaquin 19/04/10

0

Hashable có nghĩa là giá trị của một biến có thể được biểu diễn (hoặc đúng hơn là được mã hóa) bằng một hằng số - chuỗi, số, v.v. Bây giờ, thứ gì đó có thể thay đổi (có thể thay đổi) không thể được biểu diễn bằng thứ không phải. Do đó, không thể băm bất kỳ biến nào có thể thay đổi được và, bằng cách tương tự, chỉ có thể băm các biến bất biến.

Hi vọng điêu nay co ich ...

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.