2012-06-12 22 views
34

Đây là mã của tôi:Làm thế nào để tạo một đối tượng có thể băm đúng?

class Hero: 
    def __init__(self, name, age): 
     self.name = name 
     self.age = age 

    def __str__(self): 
     return self.name + str(self.age) 

    def __hash__(self): 
     print(hash(str(self))) 
     return hash(str(self)) 

heroes = set() 

heroes.add(Hero('Zina Portnova', 16)) # gets hash -8926039986155829407 
print(len(heroes)) # gets 1 

heroes.add(Hero('Lara Miheenko', 17)) # gets hash -2822451113328084695 
print(len(heroes)) # gets 2 

heroes.add(Hero('Zina Portnova', 16)) # gets hash -8926039986155829407 
print(len(heroes)) # gets 3! WHY? 

Tại sao điều này xảy ra?
Đối tượng thứ nhất và thứ 3 có cùng nội dung và cùng một mã băm nhưng len() cho biết khoảng 3 đối tượng duy nhất?

+0

Không chắc, nhưng có lẽ bạn cần '__eq__' hoặc '__cmp__' : http://docs.python.org/glossary.html#term-hashable – nhahtdh

+2

Điều đó sang một bên, đây không phải là hàm băm tốt nhất (vì bạn không băm một chuỗi chung, một trong các thành phần chuỗi có entropy thấp hơn nhiều bởi vì nó được biết là bao gồm các chữ số). Đối với một sửa chữa tầm thường nhưng khá hiệu quả, lấy giá trị băm của các đối tượng riêng biệt và xor chúng. Để có thêm ma thuật, hãy thêm chúng theo thang số nguyên tố. –

+0

@KonradRudolph: Có một giả định ngụ ý trong nhận xét của bạn - đặc biệt là một băm "tốt" là cần thiết cho các thiết lập để thực hiện tốt. Đây không phải là trường hợp với việc thực thi 'set' của Python; xem [bình luận này từ các nguồn Python] (http://hg.python.org/cpython/file/26e2ee402a0b/Objects/dictobject.c#l113) để được giải thích thêm. –

Trả lời

40

Bạn cũng cần xác định __eq__() theo cách tương thích với __hash__() - nếu không, bình đẳng sẽ được dựa trên nhận dạng đối tượng.

Trên Python 2, bạn cũng nên xác định __ne__ để làm cho != nhất quán với ==. Trên Python 3, triển khai thực hiện __ne__ mặc định sẽ ủy quyền cho __eq__ cho bạn.

+6

Thật vậy, sau khi kiểm tra xem các hash có bằng nhau không, 'dict' /' set' cũng phải kiểm tra sự bình đẳng thực sự trong trường hợp va chạm băm. –

+0

@ user2357112 Definig '__ne__' không bắt buộc phải tạo kiểu băm, phải không? Nó có thể là ý tưởng tốt để xác định nó, vì nếu không '! =' Sẽ có ngữ nghĩa khá kỳ lạ, nhưng nếu tất cả những gì bạn muốn làm là sử dụng kiểu trong tập hợp hoặc từ điển, bạn không thực sự cần nó. –

+0

@SvenMarnach: Về mặt kỹ thuật, bộ và dicts không sử dụng '! =' Ở bất cứ đâu, nhưng thực sự dựa vào đó là một công thức cho các lỗi khó chịu. Ngay cả khi bộ không sử dụng '! =', Có thể ai đó sẽ làm. Bạn có thể reword nó nếu bạn muốn, nhưng tôi nghĩ rằng câu trả lời chắc chắn nên đề cập đến '__ne__'; tinh thần của câu hỏi chắc chắn có vẻ nhiều hơn "làm thế nào để tôi làm những điều * đúng *" hơn "tối thiểu là cần thiết để có được đoạn mã này để sản xuất sản lượng dự kiến". – user2357112

6

The Python documentation có thể hữu ích:

Nếu một lớp không định nghĩa một __cmp__() hoặc __eq__() phương pháp nó không nên xác định một hoạt động __hash__() một trong hai;

8

Dưới đây là toàn bộ mã:

class Hero: 
    def __init__(self, name, age): 
     self.name = name 
     self.age = age 

    def __str__(self): 
     return self.name + str(self.age) 

    def __hash__(self): 
     print(hash(str(self))) 
     return hash(str(self)) 

    def __eq__(self,other): 
     return self.name == other.name and self.age== other.age 



heroes = set() 
heroes.add(Hero('Zina Portnova', 16)) # gets hash -8926039986155829407 
print(len(heroes)) # gets 1 

heroes.add(Hero('Lara Miheenko', 17)) # gets hash -2822451113328084695 
print(len(heroes)) # gets 2 

heroes.add(Hero('Zina Portnova', 16)) # gets hash -8926039986155829407 
print(len(heroes)) # gets 2 

Chức năng công nhận __eq__ và như len như là 2.

Các vấn đề liên quan