Đối tượng của loại tùy chỉnh làm khóa từ điển


185

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" đóng vai trò là khóa), vd

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 như các khóa được coi là giống nhau nếu tên và vị trí giống nhau. Từ C # / Java tôi đã quen với việc ghi đè và cung cấp phương thức bằng và mã băm, và hứa sẽ không làm thay đổi bất cứ điều gì mà mã băm phụ thuộc vào.

Tôi phải làm gì trong Python để thực hiện điều này? Tôi có nên thậm chí?

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


Có gì sai khi sử dụng hàm băm?
Rafe Kettler

5
Có lẽ bởi vì anh ta muốn hai MyThing, nếu chúng có cùng namelocationđể 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 như hai "đối tượng" khác nhau.
Santa

1
"có lẽ sẽ tốt hơn nếu chỉ đặt một (tên, vị trí) tuple làm khóa - nhưng xem xét tôi muốn khóa là một đối tượng)" Ý bạn là: một đối tượng NON-COMPOSITE?
Eyquem

Câu trả lời:


219

Bạn cần thêm 2 phương thức , 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)

Tài liệu dict Python xác định các yêu cầu này trên các đối tượng chính, tức là chúng phải được băm .


17
hash(self.name)trông đẹp hơn self.name.__hash__(), và nếu bạn làm và bạn có thể làm hash((x, y))để tránh XOR chính mình.
Rosh Oxymoron

5
Một ghi chú bổ sung, tôi vừa phát hiện ra rằng gọi x.__hash__()như vậy cũng sai , bởi vì nó có thể tạo ra kết quả không chính xác : pastebin.com/C9fSH7eF
Rosh Oxymoron

@Rosh Oxymoron: cảm ơn bạn đã bình luận. Khi viết tôi đã sử dụng rõ ràng andcho __eq__nhưng sau đó tôi nghĩ "tại sao không sử dụng các bộ?" bởi vì dù sao tôi cũng thường làm điều đó (tôi nghĩ nó dễ đọc hơn). Tuy nhiên, vì một số lý do kỳ lạ, mắt tôi không quay lại câu hỏi __hash__.
6502

1
@ user877329: bạn đang cố gắng sử dụng một số cấu trúc dữ liệu máy xay làm khóa? Rõ ràng từ một số repos, một số đối tượng nhất định yêu cầu bạn "đóng băng" chúng trước để tránh tính đột biến (làm biến đổi một đối tượng dựa trên giá trị đã được sử dụng làm khóa trong từ điển python không được phép)
6502

1
@ kawing-chiu pythonfiddle.com/eq-method-need-ne-method <- điều này cho thấy "lỗi" trong Python 2. Python 3 không có vấn đề này : mặc định __ne__()đã được "sửa" .
Bob Stein

33

Một cách khác trong Python 2.6 trở lên là sử dụng collections.namedtuple()- nó giúp bạn tiết kiệm bằng cách viết bất kỳ phương thức đặc biệt nào:

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

20

Bạn ghi đè __hash__nếu bạn muốn ngữ nghĩa 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 bằng nhau cần có cùng giá trị băm.

Python hy vọng sẽ __hash__trả về một số nguyên, Banana()không nên trả về :)

Các lớp __hash__do người dùng định nghĩa có mặc định gọi id(self)như bạn đã lưu ý.

Có một số lời khuyên bổ sung từ tài liệu .:

Các lớp kế thừa một __hash__() phương thức từ một lớp cha nhưng thay đổi ý nghĩa của __cmp__()hoặc __eq__() giá trị băm được trả về không còn phù hợp nữa (ví dụ: bằng cách chuyển sang một khái niệm đẳng thức dựa trên giá trị thay vì đẳng thức dựa trên danh tính mặc định) có thể tự gắn cờ rõ ràng là không thể phá vỡ bằng cách thiết lập __hash__ = None định nghĩa lớp. Làm như vậy có nghĩa là không chỉ các thể hiện của lớp sẽ tăng TypeError thích hợp khi một chương trình cố gắng truy xuất giá trị băm của chúng, mà chúng cũng sẽ được xác định chính xác là không thể xóa được khi kiểm tra isinstance(obj, collections.Hashable) (không giống như các lớp xác định __hash__()rõ ràng để tăng TypeError).


2
Băm một mình là không đủ, ngoài ra, bạn cần phải ghi đè __eq__hoặc __cmp__.
Oben Sonne

@Oben Sonne: __cmp__được Python trao cho bạn nếu đó là lớp do người dùng định nghĩa, nhưng bạn có thể muốn ghi đè chúng bằng mọi cách để 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 cmpvà 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 thực hiện để đáp ứng yêu cầu của người hỏi rằng các trường hợp có tên và vị trí tương tự có cùng khóa từ điển.
Oben Sonne
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.