Sözlük anahtarı olarak özel tür nesnesi


185

Özel bir türdeki nesneleriimi Python sözlüğünde anahtar olarak kullanmak için ne yapmalıyım (burada "nesne kimliği" nin anahtar gibi davranmasını istemiyorum), ör.

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

Ad ve konum aynı ise MyThing'in aynı olduğu düşünülen anahtarlar olarak kullanmak isterim. C # / Java den Eşit ve hashcode yöntemi geçersiz kılmak ve sağlamak zorundayım ve hashcode bağlıdır bir şey mutasyona söz veriyorum.

Bunu yapmak için Python'da ne yapmalıyım? Hatta yapmalı mıyım?

(Basit bir durumda, burada olduğu gibi, belki de bir (isim, konum) demetini anahtar olarak yerleştirmek daha iyi olur - ama anahtarın bir nesne olmasını isteyeceğimi düşünün)


Hash'i kullanmanın nesi yanlış?
Rafe Kettler

5
O ikisini istediği Muhtemelen çünkü MyThingbunlar aynı olup olmadığını nameve locationendekse iki farklı "nesneler" olarak ayrı ayrı oluşturulan bile, aynı değeri döndürmek için sözlüğü.
Noel Baba

1
"belki de sadece bir (isim, konum) tupleini anahtar olarak yerleştirmek daha iyi olurdu - ama anahtarın bir nesne olmasını isteyeceğimi düşünün" demek istiyorsun?
eyquem

Yanıtlar:


221

2 yöntem eklemeniz gerekir , not __hash__ve __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 belgeleri bu gereksinimleri anahtar nesneler üzerinde tanımlar, yani bunların yıkanabilir olması gerekir .


17
hash(self.name)daha güzel görünüyor self.name.__hash__()ve eğer yaparsanız ve hash((x, y))kendinizi XORing'den kaçınmak için yapabilirsiniz.
Rosh Oxymoron

5
Ek bir not olarak, sadece çağıran keşfetti x.__hash__()böyle de yanlış çünkü, olabilir üretmek yanlış : Sonuçları pastebin.com/C9fSH7eF
Rosh Oksimoron

@Rosh Oxymoron: Yorum için teşekkür ederim. Yazarken ben açık kullanıyordum andiçin __eq__düşündüm sonra ancak "dizilerini kullanmıyor neden?" çünkü bunu sık sık yapıyorum (daha okunaklı olduğunu düşünüyorum). Bazı tuhaf nedenlerle gözlerim yine de soruya geri dönmedi __hash__.
6502

1
@ user877329: bazı blender veri yapısını anahtar olarak kullanmaya mı çalışıyorsunuz? Görünüşe göre bazı depolarda belirli nesneler değişebilirlikten kaçınmak için önce onları "dondurmanızı" gerektirir (bir python sözlüğünde anahtar olarak kullanılan değere dayalı bir nesnenin değiştirilmesine izin verilmez)
6502

1
@ kawing-chiu pythonfiddle.com/eq-method-needs-ne-method <- bu Python 2'deki "hatayı" gösterir. Python 3 bu sorunu yok : Varsayılan __ne__()olmuştur "sabit" .
Bob Stein

34

Python 2.6 veya üzeri bir alternatif kullanmaktır collections.namedtuple()- herhangi bir özel yöntem yazmanızı sağlar:

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__Özel karma-semantik istiyorsanız geçersiz kılınır ve __cmp__veya__eq__ sınıfınızı bir anahtar olarak kullanılabilir kılmak için . Eşit karşılaştırmalı nesnelerin aynı karma değerine sahip olması gerekir.

Python __hash__, bir tamsayı döndürmeyi veBanana() önerilmez :)

Kullanıcı tanımlı sınıflar __hash__varsayılan olarakid(self) olarak, belirttiğiniz gibi .

Ekstra ipuçları var. Belgelerden :

Bir __hash__() yöntemi bir üst sınıftan devralan ancak döndürülen karma değerinin artık uygun olmayacağı __cmp__()ya da anlamını değiştiren sınıflar __eq__()(örneğin, varsayılan kimlik tabanlı eşitlik yerine değere dayalı bir eşitlik kavramına geçerek) kendilerini açıkça işaretleyebilir __hash__ = None sınıf tanımında ayar yaparak paylaşılmaz olmak . Bunu yapmak, bir program karma değerlerini almaya çalıştığında sınıf örneklerinin uygun bir TypeError değerini yükseltmekle kalmayacağı, aynı zamanda kontrol ederken isinstance(obj, collections.Hashable) ( __hash__()TypeError'u açıkça yükseltmek için kendi tanımlayan sınıfların aksine) doğru bir şekilde paylaşılamaz olarak tanımlanacağı anlamına gelir .


2
Karma tek başına yeterli değildir, ek olarak geçersiz kılmanız __eq__veya __cmp__.
Oben Sonne

@Oben Sonne: __cmp__kullanıcı tanımlı bir sınıfsa Python tarafından size verilir, ancak muhtemelen yeni semantiğe uyum sağlamak için bunları geçersiz kılmak istersiniz.
Skurmedel

1
@Skurmedel: Evet, ancak bu yöntemleri geçersiz kılmayan kullanıcı sınıflarını arayabilir cmpve kullanabilirsiniz , ancak =benzer ad ve konuma sahip örneklerin aynı sözlük anahtarına sahip olması gerektiği sorusunun sorgusunu karşılamak için bunlardan biri uygulanmalıdır.
Oben Sonne
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.