วัตถุประเภทที่กำหนดเองเป็นคีย์พจนานุกรม


185

ฉันต้องทำอย่างไรเพื่อใช้วัตถุที่กำหนดเองเป็นประเภทในพจนานุกรม Python (ที่ฉันไม่ต้องการให้ "object id" ทำหน้าที่เป็นกุญแจสำคัญ) เช่น

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

ฉันต้องการใช้ MyThing เป็นกุญแจที่ถือว่าเหมือนกันหากชื่อและที่ตั้งเหมือนกัน จาก C # / Java ฉันเคยต้องลบล้างและให้วิธีเท่ากับและ hashcode และสัญญาว่าจะไม่กลายพันธุ์อะไร hashcode ขึ้นอยู่กับ

ฉันต้องทำอะไรใน Python เพื่อทำสิ่งนี้ให้สำเร็จ ฉันควรทำยัง

(ในกรณีอย่างง่ายเช่นที่นี่อาจจะเป็นการดีกว่าถ้าวาง tuple (ชื่อสถานที่) เป็นกุญแจ - แต่ให้พิจารณาว่าฉันต้องการให้กุญแจเป็นวัตถุ)


เกิดอะไรขึ้นกับการใช้แฮช
Rafe Kettler

5
อาจเป็นเพราะเขาต้องการสองMyThingถ้าพวกเขามีเหมือนกันnameและlocationเพื่อจัดทำดัชนีพจนานุกรมเพื่อส่งกลับค่าเดียวกันแม้ว่าพวกเขาจะถูกสร้างแยกเป็นสอง "วัตถุ" ที่แตกต่างกัน
Santa

1
"น่าจะดีกว่าถ้าวาง tuple (ชื่อสถานที่) เป็นกุญแจ - แต่ให้พิจารณาว่าฉันต้องการให้กุญแจเป็นวัตถุ)" คุณหมายถึง: วัตถุที่ไม่ใช่คอมโพสิต
eyquem

คำตอบ:


221

คุณต้องเพิ่ม2 วิธีการบันทึก__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)

เอกสาร Python dictกำหนดข้อกำหนดเหล่านี้ไว้บนวัตถุสำคัญกล่าวคือต้องมีการแฮ


17
hash(self.name)ดูดีกว่าself.name.__hash__()และถ้าคุณทำและคุณสามารถทำได้hash((x, y))เพื่อหลีกเลี่ยง XORing ตัวเอง
Rosh Oxymoron

5
ในฐานะที่เป็นบันทึกเพิ่มเติมฉันเพิ่งค้นพบว่าการโทรx.__hash__()แบบนั้นก็ผิดเพราะมันสามารถให้ผลลัพธ์ที่ไม่ถูกต้อง : pastebin.com/C9fSH7eF
Rosh Oxymoron

@Rosh Oxymoron: ขอบคุณสำหรับความคิดเห็น เมื่อเขียนฉันถูกใช้อย่างชัดเจนandสำหรับ__eq__แต่แล้วฉันคิดว่า "ทำไมไม่ใช้ tuples?" เพราะฉันมักจะทำอย่างนั้นอยู่ดี (ฉันคิดว่ามันอ่านง่ายขึ้น) ด้วยเหตุผลบางอย่างที่แปลกตาของฉันไม่ได้กลับไปถามเกี่ยวกับ__hash__อย่างไรก็ตาม
6502

1
@ user877329: คุณพยายามใช้โครงสร้างข้อมูลของเครื่องปั่นบางอย่างเป็นกุญแจหรือไม่? เห็นได้ชัดจากบาง repos วัตถุบางอย่างต้องการให้คุณ "หยุด" พวกเขาก่อนเพื่อหลีกเลี่ยงความไม่แน่นอน (การกลายพันธุ์วัตถุตามค่าที่ใช้เป็นกุญแจสำคัญในพจนานุกรมหลามไม่ได้รับอนุญาต)
6502

1
@ kawing-Chiu pythonfiddle.com/eq-method-needs-ne-method <- นี้แสดงให้เห็นว่า "ข้อผิดพลาด" ในหลาม 2. หลาม 3 ไม่ได้มีปัญหานี้ : เริ่มต้น__ne__()ได้รับการ"คงที่"
Bob Stein

34

ทางเลือกอื่นใน Python 2.6 หรือสูงกว่านั้นคือการใช้collections.namedtuple()มันช่วยให้คุณไม่ต้องเขียนวิธีพิเศษใด ๆ :

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

คุณลบล้าง__hash__ถ้าคุณต้องการแฮช - ซีแมนทิกส์พิเศษและ__cmp__หรือ__eq__เพื่อให้ชั้นเรียนของคุณสามารถใช้เป็นกุญแจได้ วัตถุที่เปรียบเทียบกันต้องมีค่าแฮชเหมือนกัน

Python คาดว่า__hash__จะคืนค่าจำนวนเต็มBanana()ไม่แนะนำให้ส่งคืน:)

คลาสที่ผู้ใช้กำหนดมี__hash__ค่าเริ่มต้นที่เรียกid(self)ตามที่คุณบันทึกไว้

มีเคล็ดลับเพิ่มเติมจากเอกสารประกอบ :

คลาสที่สืบทอด__hash__() เมธอดจากคลาสพาเรนต์ แต่เปลี่ยนความหมายของ__cmp__()หรือ__eq__() ว่าค่าแฮชที่ส่งคืนนั้นไม่เหมาะสมอีกต่อไป (เช่นโดยการเปลี่ยนไปใช้แนวคิดที่อิงตามมูลค่าของความเท่าเทียมกันแทนที่จะเป็นค่าเริ่มต้นตามความเสมอภาคตามตัวตน) เป็น unhashable โดยการตั้งค่า__hash__ = None ในการกำหนดระดับ การทำเช่นนี้หมายความว่าไม่เพียง แต่จะกรณีของการเพิ่มระดับชั้น TypeError เหมาะสมเมื่อความพยายามโปรแกรมเพื่อดึงค่าแฮชของพวกเขา แต่พวกเขายังจะระบุได้อย่างถูกต้องตาม unhashable เมื่อตรวจสอบ isinstance(obj, collections.Hashable) (ซึ่งแตกต่างจากการเรียนที่กำหนดของตัวเอง __hash__()ที่จะยกระดับอย่างชัดเจน TypeError)


2
กัญชาอย่างเดียวไม่พอนอกจากนี้คุณอาจจำเป็นต้องแทนที่หรือ__eq__ __cmp__
Oben Sonne

@Oben Sonne: __cmp__Python มอบให้คุณหากเป็นคลาสที่ผู้ใช้กำหนด แต่คุณอาจต้องการแทนที่พวกเขาต่อไปเพื่อรองรับซีแมนทิกส์ใหม่
Skurmedel

1
@Skurmedel: ใช่ แต่แม้ว่าคุณสามารถโทรcmpและใช้=ในคลาสผู้ใช้ที่ไม่ได้แทนที่วิธีการเหล่านี้หนึ่งในนั้นจะต้องดำเนินการเพื่อตอบสนองความต้องการของผู้ถามว่าอินสแตนซ์ที่มีชื่อและสถานที่คล้ายกันมีคีย์พจนานุกรมเดียวกัน
Oben Sonne
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.