Câu hỏi Làm thế nào để thực hiện một hàm __hash__ tốt trong python


Khi triển khai một lớp với nhiều thuộc tính (như trong ví dụ đồ chơi bên dưới), cách tốt nhất để xử lý băm là gì?

Tôi đoán rằng __eq__ và __hash__ nên nhất quán, nhưng làm thế nào để thực hiện một hàm băm thích hợp có khả năng xử lý tất cả các thuộc tính?

class AClass:
  def __init__(self):
      self.a = None
      self.b = None

  def __eq__(self, other):
      return other and self.a == other.a and self.b == other.b

  def __ne__(self, other):
    return not self.__eq__(other)

  def __hash__(self):
      return hash((self.a, self.b))

Tôi đã đọc câu hỏi này tuples có thể băm, vì vậy tôi đã tự hỏi nếu một cái gì đó giống như ví dụ trên là hợp lý. Là nó?


76
2017-10-23 17:54


gốc


Chỉ cần đảm bảo sử dụng hash() trên một tuple với chính xác các yếu tố được so sánh trong __eq__() và bạn bè (chính xác như bạn đã làm) và bạn tốt để đi. - Feuermurmel
Bản sao xác định của Một cách chính xác và tốt để thực hiện __hash __ () là gì? - Jean-François Corbett


Các câu trả lời:


__hash__ nên trả lại cùng một giá trị cho các đối tượng bằng nhau. Nó cũng không nên thay đổi trong suốt vòng đời của vật thể; nói chung bạn chỉ thực hiện nó cho các đối tượng bất biến.

Việc triển khai tầm thường sẽ chỉ là return 0. Điều này luôn đúng, nhưng hoạt động kém.

Giải pháp của bạn, trả về giá trị băm của một bộ tài sản, là tốt. Nhưng lưu ý rằng bạn không cần liệt kê tất cả các thuộc tính mà bạn so sánh __eq__ trong tuple. Nếu một số thuộc tính thường có cùng giá trị cho các đối tượng bất bình đẳng, chỉ cần bỏ qua nó. Đừng làm cho việc tính toán băm tốn kém hơn mức cần thiết.

Chỉnh sửa: Tôi sẽ khuyên bạn nên chống lại bằng cách sử dụng xor để trộn băm nói chung. Khi hai thuộc tính khác nhau có cùng giá trị, chúng sẽ có cùng một giá trị băm, và với xor chúng sẽ hủy lẫn nhau. Tuples sử dụng một phép tính phức tạp hơn để trộn băm, xem tuplehash trong tupleobject.c.


56
2017-10-23 18:19



Như bạn đã nói hàm băm thường chỉ có ý nghĩa đối với các đối tượng bất biến. Do đó có thể tính giá trị băm một lần trong __init__. - Björn Pollex
+1 cho return 0 hàm băm - Tôi đã luôn luôn nghĩ rằng bất cứ điều gì khác là tối ưu hóa sớm :-). (Tôi chỉ đùa thôi). - Scott Griffiths
@ BjörnPollex Thay vì làm điều đó trong __init__, bạn chỉ có thể lưu trữ giá trị trong __hash__. Bằng cách đó nếu __hash__ không bao giờ được gọi, bạn không lãng phí thời gian hoặc bộ nhớ. Tôi giả sử kiểm tra xem giá trị đã được lưu trữ không phải là đắt tiền phải không? (Không chắc chắn nếu nó tốt nhất thông qua ngoại lệ hoặc rõ ràng if). - max
Thật không may là Python không tạo ra combine_hashes chức năng có sẵn. - Fred Foo
Nó không được thực hiện trong những thứ như dict hoặc danh sách, biện minh cho rằng việc thay đổi giá trị băm của đối tượng đã thuộc về, ví dụ: một tập hợp làm hỏng cấu trúc dữ liệu nội bộ của bộ. - javawizard


Thật nguy hiểm khi viết

def __eq__(self, other):
  return other and self.a == other.a and self.b == other.b

bởi vì nếu rh của bạn (tức là, other) đối tượng đánh giá là boolean False, nó sẽ không bao giờ so sánh với bất kỳ thứ gì!

Ngoài ra, bạn có thể muốn kiểm tra lại nếu other thuộc về lớp hoặc phân lớp của AClass. Nếu không, bạn sẽ có ngoại lệ AttributeError hoặc dương tính giả (nếu lớp khác xảy ra có cùng thuộc tính được đặt tên với các giá trị khớp). Vì vậy, tôi khuyên bạn nên viết lại __eq__ như:

def __eq__(self, other):
  return isinstance(other, self.__class__) and self.a == other.a and self.b == other.b

Nếu có cơ hội bạn muốn so sánh linh hoạt bất thường, so sánh giữa các lớp không liên quan miễn là các thuộc tính khớp với tên, bạn vẫn muốn ít nhất tránh AttributeError và kiểm tra xem other không có bất kỳ thuộc tính bổ sung nào. Làm thế nào bạn làm điều đó phụ thuộc vào tình hình (vì không có cách tiêu chuẩn để tìm tất cả các thuộc tính của một đối tượng).


12
2017-09-20 11:31



Thông tin hữu ích, nhưng không liên quan đến câu hỏi chính về băm. - Mad Physicist


Tài liệu cho object.__hash__(self)

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ó được khuyến khích bằng cách nào đó trộn lẫn với nhau (ví dụ: sử dụng độc quyền hoặc) giá trị băm cho các thành phần của đối tượng cũng đóng vai trò so sánh các đối tượng.

def __hash__(self):
    return hash(self.a) ^ hash(self.b)

9
2017-10-23 18:11



Nó sẽ hoạt động, nhưng thật tệ nếu bạn trao đổi self.a và self.b sau đó bạn sẽ nhận được cùng một băm trong khi nó sẽ là "đối tượng" khác. - eigenein
"bằng cách nào đó kết hợp với nhau (ví dụ: sử dụng độc quyền hoặc" là một bộ yêu cầu khá linh hoạt. Nếu nó thực sự quan trọng, thì (hash(self.a)<<1) ^ hash(self.b) có thể tốt hơn. Không có câu trả lời chung, chỉ là một hướng dẫn chung mà phải được sửa đổi dựa trên ứng dụng cụ thể. - S.Lott
tại sao không chỉ băm một giá trị tuple? băm ((self.a, self.b)) - nightpool
Lưu ý rằng (may mắn thay) gợi ý sử dụng xor không còn tồn tại trong Python 3 hoặc là Python 2 tài liệu. - PM 2Ring
Đối với những người quan tâm, đây là lỗi dẫn đến việc xóa đề xuất XOR: bugs.python.org/issue28383 - AXO