Python'da bir listeyi dikt anahtarı olarak neden kullanamıyorum?


106

Bir python diktesi için neyin anahtar olarak kullanılabileceği / kullanılamayacağı konusunda biraz kafam karıştı.

dicked = {}
dicked[None] = 'foo'     # None ok
dicked[(1,3)] = 'baz'    # tuple ok
import sys
dicked[sys] = 'bar'      # wow, even a module is ok !
dicked[(1,[3])] = 'qux'  # oops, not allowed

Yani bir demet değişmez bir türdür, ancak içindeki bir listeyi gizlersem, o zaman anahtar olamaz .. Bir modülün içindeki bir listeyi bu kadar kolay gizleyemez miyim?

Anahtarın "hashable" olması gerektiğine dair belirsiz bir fikrim vardı, ancak teknik detaylar hakkındaki kendi cehaletimi kabul edeceğim; Burada gerçekten neler olduğunu bilmiyorum. Listeleri anahtar olarak, diyelim ki hafıza konumlarıyla birlikte kullanmaya çalışırsanız ne yanlış olur?


1
İşte güzel bir tartışma: stackoverflow.com/questions/2671211/…
Hernan

51
Değişken adınızdan bir kıkırdama geldi.
kindall

Yanıtlar:


36

Python wiki'de konuyla ilgili güzel bir makale var: Listeler Neden Sözlük Anahtarları Olmaz . Orada açıklandığı gibi:

Listeleri anahtar olarak, diyelim ki hafıza konumlarıyla birlikte kullanmaya çalışırsanız ne yanlış olur?

Gereksinimlerin hiçbirini gerçekten bozmadan yapılabilir, ancak beklenmedik davranışlara yol açar. Listeler genellikle, örneğin (in-) eşitliği kontrol edilirken, değerleri içeriklerinin değerlerinden türetilmiş gibi ele alınır. Birçoğu - anlaşılır bir şekilde - [1, 2]aynı anahtarı almak için herhangi bir listeyi kullanabileceğinizi , burada tam olarak aynı liste nesnesini tutmanız gerektiğini bekler . Ancak, anahtar olarak kullanılan bir liste değiştirilir değiştirilmez değere göre arama bozulur ve kimliğe göre arama, tam olarak aynı listeyi tutmanızı gerektirir - bu, başka herhangi bir genel liste işlemi için gerekli değildir (en azından aklıma gelmeyen hiçbiri ).

Modüller gibi diğer nesneler ve objectnesne kimliklerinden çok daha büyük bir anlaşma yaparlar (en son ne zaman iki farklı modül nesnesi olarak adlandırılmıştınız sys?) Ve yine de bununla karşılaştırılırlar. Bu nedenle, dikte anahtarları olarak kullanıldıklarında, bu durumda da kimliğe göre karşılaştırmaları daha az şaşırtıcıdır - hatta beklenir.


33

Python'da bir listeyi dikt anahtarı olarak neden kullanamıyorum?

>>> d = {repr([1,2,3]): 'value'}
{'[1, 2, 3]': 'value'}

(Bu soruya tökezleyenler, bunun etrafında bir yol arayanlar için)

burada başkaları tarafından açıklandığı gibi, gerçekten yapamazsınız. Bununla birlikte, listenizi gerçekten kullanmak istiyorsanız, bunun yerine dize temsilini kullanabilirsiniz.


6
Üzgünüm, ne demek istediğini gerçekten anlamıyorum. Anahtar olarak dize değişmezlerini kullanmaktan farklı değildir.
wim

12
Doğru; Listeleri neden 'anahtar hashable olmalıdır' açısından kullanamayacağınızı açıklayan o kadar çok cevap gördüm ki bu o kadar doğru ki, sadece (yeni) biri ararsa diye, bununla ilgili bir yol önermek istedim. ...
Remi

5
Neden listeyi bir demete dönüştürmüyorsunuz? Neden onu bir dizgeye dönüştürelim? Bir demet kullanırsanız, özel bir karşılaştırma yöntemi olan sınıflarla doğru şekilde çalışacaktır __eq__. Ancak onları dizelere dönüştürürseniz, her şey dizgi temsiline göre karşılaştırılır.
Aran-Fey

iyi nokta @ Aran-Fey. Demetteki herhangi bir öğenin kendisinin hashable olduğundan emin olun. Örneğin, bir anahtar olarak tuple ([[1,2], [2,3]]) çalışmayacaktır çünkü başlığın elemanları hala liste halindedir.
Remi

20

Listeyi tuple olarak değiştirebilir, ardından anahtar olarak kullanabilirsiniz.

d = {tuple([1,2,3]): 'value'}

bir cazibe gibi çalıştı!
Tabz

16

Sorun, tupleların değişmez olması ve listelerin olmamasıdır. Aşağıdakileri göz önünde bulundur

d = {}
li = [1,2,3]
d[li] = 5
li.append(4)

Ne d[li]iade edilmeli ? Aynı liste mi? Nasıl olur d[[1,2,3]]? Aynı değerlere sahip ama farklı bir liste mi?

Sonuçta tatmin edici bir cevap yok. Örneğin, çalışan tek anahtar orijinal anahtar ise, o anahtara referansınız yoksa, değere bir daha asla erişemezsiniz. İzin verilen diğer tüm anahtarlarla, orijinaline başvurmadan bir anahtar oluşturabilirsiniz.

Önerilerimin ikisi de işe yararsa, aynı değeri döndüren çok farklı anahtarlarınız var, bu biraz şaşırtıcı değil. Yalnızca orijinal içerik çalışırsa, listeler değiştirilmek üzere yapıldığından anahtarınız hızla bozulacaktır.


Evet, liste aynı, bu yüzden d[li]5 olarak kalmayı bekliyorum d[[1,2,3]], anahtar olarak farklı bir liste nesnesine atıfta bulunacağım, bu nedenle bu bir KeyError olacaktır. Henüz bir sorun görmüyorum .. Bir anahtarın çöpün toplanmasına izin vermenin bazı dikt değerlerini erişilemez hale getirmesi dışında. Ama bu pratik bir sorun mantıksal bir sorun değil ..
wim

@wim: KeyError d[list(li)]olmak sorunun bir parçasıdır. Hemen hemen her diğer kullanım durumunda , liaynı içeriğe sahip yeni bir listeden ayırt edilemez. İşe yarıyor, ancak birçokları için mantıksız. Geçen sefer olduğu zaman Artı, gerçekten dict anahtarı olarak bir listesini kullanmak zorunda? Hayal edebileceğim tek kullanım durumu, yine de her şeyi kimliğine göre hash ettiğinizde ve bu durumda, güvenmek __hash__ve __eq__kimlik temelli olmak yerine bunu yapmalısınız .

@delnan Sorun basitçe, bu tür komplikasyonlar nedeniyle çok yararlı bir dikt olmaması mı ? yoksa gerçekten bir emri bozmasının bir nedeni var mı?
wim

2
@wim: İkincisi. Cevabımda da belirtildiği gibi, dikt anahtarlarla ilgili gereksinimleri gerçekten bozmaz, ancak çözdüğünden daha fazla sorun ortaya çıkarması muhtemeldir.

1
@delnan - sen 'eski' demek istedi
Jason

9

İşte bir cevap http://wiki.python.org/moin/DictionaryKeys

Listeleri anahtar olarak, diyelim ki hafıza konumlarıyla birlikte kullanmaya çalışırsanız ne yanlış olur?

Aynı içeriğe sahip farklı listelerin aranması farklı sonuçlar doğuracaktır, ancak aynı içeriğe sahip listeleri karşılaştırmak bunların eşdeğer olduğunu gösterecektir.

Sözlük aramasında bir liste değişmezi kullanmaya ne dersiniz?


4

Listeler değiştirilebilir olduğu için, dictanahtarlar (ve setüyeleri) ihtiyaç hashable olmak ve karma değerleri nedeniyle değişken nesneleri karma kötü bir fikir olmalıdır örneği davranışına göre hesaplanabilir.

Bu cevapta bazı somut örnekler vereceğim, umarım mevcut cevapların üstüne değer katar. Her içgörü, veri yapısının öğeleri için de geçerlidir set.

Örnek 1 : Karma değerin nesnenin değiştirilebilir bir özelliğine dayandığı bir değiştirilebilir nesneye hashing uygulamak.

>>> class stupidlist(list):
...     def __hash__(self):
...         return len(self)
... 
>>> stupid = stupidlist([1, 2, 3])
>>> d = {stupid: 0}
>>> stupid.append(4)
>>> stupid
[1, 2, 3, 4]
>>> d
{[1, 2, 3, 4]: 0}
>>> stupid in d
False
>>> stupid in d.keys()
False
>>> stupid in list(d.keys())
True

Mutasyona uğratıldıktan sonra stupid, karma değiştiği için artık diktede bulunamaz. Yalnızca diktenin anahtarlarının listesi üzerinde doğrusal bir tarama bulunur stupid.

Örnek 2 : ... ama neden sabit bir hash değeri olmasın?

>>> class stupidlist2(list):
...     def __hash__(self):
...         return id(self)
... 
>>> stupidA = stupidlist2([1, 2, 3])
>>> stupidB = stupidlist2([1, 2, 3])
>>> 
>>> stupidA == stupidB
True
>>> stupidA in {stupidB: 0}
False

Bu da iyi bir fikir değil çünkü eşit nesneler, onları a dictveya içinde bulabileceğiniz şekilde özdeş olarak karma yapmalıdır set.

Örnek 3 : ... tamam, peki ya tüm örneklerdeki sabit karmalar ?!

>>> class stupidlist3(list):
...     def __hash__(self):
...         return 1
... 
>>> stupidC = stupidlist3([1, 2, 3])
>>> stupidD = stupidlist3([1, 2, 3])
>>> stupidE = stupidlist3([1, 2, 3, 4])
>>> 
>>> stupidC in {stupidD: 0}
True
>>> stupidC in {stupidE: 0}
False
>>> d = {stupidC: 0}
>>> stupidC.append(5)
>>> stupidC in d
True

İşler beklendiği gibi çalışıyor gibi görünüyor, ancak neler olduğunu bir düşünün: Sınıfınızın tüm örnekleri aynı hash değerini ürettiğinde, a'da dictveya a'da anahtar olarak ikiden fazla örnek olduğunda bir hash çarpışması yaşarsınız set.

Doğru örneği my_dict[key]veya key in my_dict(veya item in my_set) ile bulmak, stupidlist3diktenin anahtarlarında (en kötü durumda) olduğu kadar çok eşitlik kontrolü gerçekleştirmelidir . Bu noktada, sözlüğün amacı - O (1) araması - tamamen bozulur. Bu, aşağıdaki zamanlamalarda gösterilmiştir (IPython ile yapılır).

Örnek 3 için Bazı Zamanlamalar

>>> lists_list = [[i]  for i in range(1000)]
>>> stupidlists_set = {stupidlist3([i]) for i in range(1000)}
>>> tuples_set = {(i,) for i in range(1000)}
>>> l = [999]
>>> s = stupidlist3([999])
>>> t = (999,)
>>> 
>>> %timeit l in lists_list
25.5 µs ± 442 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit s in stupidlists_set
38.5 µs ± 61.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit t in tuples_set
77.6 ns ± 1.5 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Gördüğünüz gibi , bir sürü hash çarpışması olmayan bir sette beklenen süper hızlı arama süresine (faktör 500) sahip olurken , bizim üyelik testi stupidlists_setbütün olarak doğrusal bir taramadan bile daha yavaştır lists_list.


TL; DR: anahtarlar tuple(yourlist)olarak kullanabilirsiniz dict, çünkü başlıklar değişmez ve karıştırılabilirdir.


>>> x = (1,2,3321321321321,) >>> id (x) 139936535758888 >>> z = (1,2,3321321321321,) >>> id (z) 139936535760544 >>> id ((1, 2,3321321321321,)) 139936535810768 Bu 3 aynı demet değerlerine ancak farklı kimliklere sahip. Yani x anahtarına sahip bir sözlüğün z anahtarı için herhangi bir değeri olmayacak mı?
Ashwani

@Ashwani denedin mi?
timgeb

Evet, beklendiği gibi çalışıyor, şüphem aynı değerlere sahip tüm demetlerin farklı kimlikleri var. Peki bu hash hangi temelde hesaplanıyor?
Ashwani

@Ashwani'nin karması xve zaynı. Bununla ilgili net olmayan bir şey varsa, lütfen yeni bir soru açın.
timgeb

1
@Ashwani hash(x)ve hash(z).
timgeb

3

Tenteniz burada bulunabilir:

Listeler Neden Sözlük Anahtarı Olmaz?

Python'a yeni gelenler, dilin hem bir demet hem de bir liste türü içerdiği halde, listelerin sözlük anahtarı olarak kullanılmasının nedenini genellikle merak eder. Bu kasıtlı bir tasarım kararıydı ve en iyi ilk önce Python sözlüklerinin nasıl çalıştığını anlamakla açıklanabilir.

Kaynak ve daha fazla bilgi: http://wiki.python.org/moin/DictionaryKeys


3

Sorunuzun basit cevabı, sınıf listesinin, sözlükte anahtar olarak kullanılmasını isteyen herhangi bir nesne için gerekli olan yöntem karmasını uygulamadığıdır . Ancak sebebi karma listesini düzenlemenin listeyi anlamına gelebilir yeniden hesaplanması karma gerektirecektir yüzden bir liste değişken olduğu için de (kabın içeriğine göre) tuple sınıfını verir ki aynı şekilde uygulanmadı olduğunu artık temel hash tablosundaki yanlış kovada bulunuyor. Bir demeti değiştiremeyeceğiniz için (değişmez), bunun bu problemle karşılaşmadığını unutmayın.

Bir yan not olarak, dictobjects aramasının gerçek uygulaması, Knuth Vol. 3, Sec. 6.4. Elinizde bu kitap varsa, okumaya değer olabilir, ayrıca gerçekten, gerçekten ilgileniyorsanız, burada dictobject'in gerçek uygulamasıyla ilgili geliştirici yorumlarına bir göz atmak isteyebilirsiniz. Tam olarak nasıl çalıştığına dair büyük ayrıntılara giriyor. Ayrıca, ilgilenebileceğiniz sözlüklerin uygulanması üzerine bir python dersi de vardır . Bir anahtarın tanımını ve ilk birkaç dakikada bir karmanın ne olduğunu gözden geçirirler.


-1

Python 2.7.2 belgelerine göre:

Bir nesne, ömrü boyunca hiçbir zaman değişmeyen bir karma değerine sahipse (bir hash () yöntemine ihtiyaç duyuyorsa ) ve diğer nesnelerle karşılaştırılabiliyorsa (bir eq () veya cmp () yöntemine ihtiyaç duyuyorsa ) karma hale getirilebilir . Eşitleri karşılaştıran karma işlem yapılabilir nesneler aynı karma değerine sahip olmalıdır.

Hashability, bir nesneyi bir sözlük anahtarı ve bir set üyesi olarak kullanılabilir kılar, çünkü bu veri yapıları, hash değerini dahili olarak kullanır.

Değiştirilebilir kaplar (listeler veya sözlükler gibi) yokken, Python'un değişmez yerleşik nesnelerinin tümü karma hale getirilebilir. Kullanıcı tanımlı sınıfların örnekleri olan nesneler varsayılan olarak karma hale getirilebilir; hepsi eşit olmayanları karşılaştırırlar ve hash değerleri id () 'dir.

Bir demet, elemanlarını ekleyemeyeceğiniz, kaldıramayacağınız veya değiştiremeyeceğiniz anlamında değişmezdir, ancak elemanların kendileri değişebilir. Listenin hash değeri, öğelerinin hash değerlerine bağlıdır ve bu nedenle, öğeleri değiştirdiğinizde değişir.

Liste karmaları için id'lerin kullanılması, tüm listelerin farklı şekilde karşılaştırıldığı anlamına gelir, bu şaşırtıcı ve rahatsız edici olur.


1
Sorunun cevabı bu değil, değil mi? hash = idilk paragrafın sonundaki değişmezi bozmaz, soru neden bu şekilde yapılmadığıdır.

@delnan: Netleştirmek için son paragrafı ekledim.
Nicola Musatti

-1

Sözlük, anahtarlarınızın haritasını, karma yeni anahtara dönüştürülen değeri ve değer eşlemesini depolayan bir HashMap'tir.

(psuedo kodu) gibi bir şey:

{key : val}  
hash(key) = val

Sözlüğünüz için anahtar olarak kullanılabilecek mevcut seçeneklerin hangileri olduğunu merak ediyorsanız. Sonra

Hashable olan herhangi bir şey (hash'e dönüştürülebilir ve statik değeri tutabilir, yani yukarıda belirtildiği gibi bir hashed anahtar yapmak için değişmez) uygundur, ancak liste veya set nesneleri hareket halindeyken değişiklik gösterebileceğinden, hash (key) de ihtiyaç duymalıdır sadece listenizle veya kümenizle senkronize olmak için değişiklik yapmak.

Deneyebilirsin :

hash(<your key here>)

İyi çalışıyorsa, sözlüğünüz için anahtar olarak kullanılabilir veya başka bir şekilde onu karma bir şeye dönüştürebilirsiniz.


Kısacası :

  1. Bu listeyi şuna dönüştür tuple(<your list>).
  2. Bu listeyi şuna dönüştür str(<your list>).

-1

dictanahtarların hashable olması gerekir. Listeler Değişebilirdir ve geçerli bir hash yöntemi sağlamazlar .

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.