2011-02-04 49 views
146

Tôi phải làm gì để sử dụng các đối tượng thuộc loại tùy chỉnh làm khóa trong từ điển Python (nơi tôi không muốn "id đối tượng" hoạt động như khóa), ví dụ:Đối tượng của loại tùy chỉnh là khóa từ điển

class MyThing: 
    def __init__(self,name,location,length): 
      self.name = name 
      self.location = location 
      self.length = length 

Tôi muốn sử dụng MyThing làm khóa được coi là giống nhau nếu tên và vị trí giống nhau. Từ C#/Java, tôi được sử dụng để ghi đè và cung cấp phương thức equals và hashcode, và hứa sẽ không thay đổi bất cứ điều gì hashcode phụ thuộc vào.

Tôi phải làm gì trong Python để thực hiện việc này? Tôi có nên không?

(Trong một trường hợp đơn giản, giống như ở đây, có lẽ nó muốn được tốt hơn để chỉ cần đặt một (tên, địa điểm) tuple như chìa khóa - nhưng xem xét Tôi muốn chìa khóa để trở thành một đối tượng)

+0

Có gì sai với việc sử dụng băm? –

+1

Có lẽ vì anh ta muốn hai 'MyThing', nếu họ có cùng' tên' và 'vị trí', để lập chỉ mục từ điển trả về cùng một giá trị, ngay cả khi chúng được tạo riêng biệt như hai" đối tượng "khác nhau. – Santa

+1

"có lẽ tốt hơn là chỉ cần đặt một (tên, vị trí) tuple là chìa khóa - nhưng xem xét tôi muốn chìa khóa là một đối tượng)" Bạn có nghĩa là: một đối tượng không COMPOSITE? – eyquem

Trả lời

168

Bạn cần phải thêm 2 methods, lưu ý __hash____eq__:

class MyThing: 
    def __init__(self,name,location,length): 
     self.name = name 
     self.location = location 
     self.length = length 

    def __hash__(self): 
     return hash((self.name, self.location)) 

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

    def __ne__(self, other): 
     # Not strictly necessary, but to avoid having both x==y and x!=y 
     # True at the same time 
     return not(self == other) 

Các Python dict documentation xác định các yêu cầu trên các đối tượng chủ chốt, tức là họ phải hashable.

+15

'băm (self.name)' trông đẹp hơn 'self.name .__ hash __()', và nếu bạn làm và bạn có thể làm 'băm ((x, y))' để tránh XORing mình. –

+3

Là một lưu ý bổ sung, tôi vừa phát hiện ra rằng việc gọi 'x .__ hash __()' giống như vậy cũng là * sai *, vì nó _can_ tạo ra kết quả _incorrect_: http://pastebin.com/C9fSH7eF –

+0

@Rosh Oxymoron: cảm ơn bạn đã lời bình luận. Khi viết tôi đã sử dụng 'và' rõ ràng cho '__eq__' nhưng sau đó tôi nghĩ" tại sao không dùng tuple? " bởi vì tôi thường làm điều đó (tôi nghĩ nó dễ đọc hơn). Đối với một số lý do lạ mắt của tôi đã không trở lại câu hỏi về '__hash__' tuy nhiên. – 6502

18

Bạn ghi đè __hash__ nếu bạn muốn các thuật ngữ băm đặc biệt và __cmp__ hoặc __eq__ để làm cho lớp của bạn có thể sử dụng làm khóa. Các đối tượng so sánh nhu cầu bằng nhau để có cùng giá trị băm.

Python hy vọng __hash__ để trả lại một số nguyên, trở về Banana() không được khuyến khích :)

Người dùng xác định các lớp học có __hash__ theo mặc định mà các cuộc gọi id(self), như bạn đã nói.

Có một số lời khuyên thêm từ documentation:.

Lớp học mà kế thừa một phương pháp __hash__() từ một lớp cha mẹ nhưng thay đổi ý nghĩa của __cmp__() hoặc __eq__() như vậy mà giá trị băm trở lại là không dài hơn thích hợp (ví dụ: bằng cách chuyển sang khái niệm giá trị dựa trên bình đẳng thay vì mặc định căn cước dựa trên nhận dạng) có thể gắn cờ rõ ràng là không thể chỉnh sửa bằng cách đặt __hash__ = None trong định nghĩa lớp học. Làm như vậy có nghĩa là không chỉ sẽ thể hiện của lớp nâng cao một TypeError thích hợp khi một chương trình cố gắng lấy giá trị băm của họ, nhưng họ cũng sẽ được xác định một cách chính xác như unhashable khi kiểm tra isinstance(obj, collections.Hashable) (không giống như các lớp học mà xác định riêng của họ __hash__() để tăng rõ ràng TypeError).

+2

Chỉ riêng hàm băm là không đủ, bạn cần phải ghi đè lên '__eq__' hoặc' __cmp__'. –

+0

@Oben Sonne: '__cmp__' được Python cung cấp cho bạn nếu nó là một lớp do người dùng định nghĩa, nhưng bạn có thể vẫn muốn ghi đè lên chúng để phù hợp với ngữ nghĩa mới. – Skurmedel

+1

@ Skurmedel: Có, nhưng mặc dù bạn có thể gọi 'cmp' và sử dụng' = 'trên các lớp người dùng không ghi đè các phương thức này, một trong số chúng phải được triển khai để đáp ứng yêu cầu của người hỏi. khóa từ điển. –

28

Một thay thế trong Python 2.6 hoặc cao hơn là sử dụng collections.namedtuple() - nó giúp bạn tiết kiệm bằng văn bản bất kỳ phương pháp đặc biệt:

from collections import namedtuple 
MyThingBase = namedtuple("MyThingBase", ["name", "location"]) 
class MyThing(MyThingBase): 
    def __new__(cls, name, location, length): 
     obj = MyThingBase.__new__(cls, name, location) 
     obj.length = length 
     return obj 

a = MyThing("a", "here", 10) 
b = MyThing("a", "here", 20) 
c = MyThing("c", "there", 10) 
a == b 
# True 
hash(a) == hash(b) 
# True 
a == c 
# False 
Các vấn đề liên quan