Perché non posso usare un elenco come chiave di comando in Python?


100

Sono un po 'confuso su cosa può / non può essere usato come chiave per un dict di Python.

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

Quindi una tupla è un tipo immutabile ma se nascondo un elenco al suo interno, allora non può essere una chiave .. non potrei nascondere altrettanto facilmente un elenco all'interno di un modulo?

Avevo una vaga idea che la chiave dovesse essere "modificabile", ma ammetto solo la mia ignoranza sui dettagli tecnici; Non so cosa stia realmente succedendo qui. Cosa andrebbe storto se provassi a utilizzare gli elenchi come chiavi, con l'hash come, ad esempio, la loro posizione di memoria?


1
Ecco una buona discussione: stackoverflow.com/questions/2671211/…
Hernan

49
Ho una risatina dal nome della tua variabile.
kindall

Risposte:


33

C'è un buon articolo sull'argomento nel wiki di Python: Perché gli elenchi non possono essere chiavi del dizionario . Come spiegato lì:

Cosa andrebbe storto se provassi a utilizzare gli elenchi come chiavi, con l'hash come, ad esempio, la loro posizione di memoria?

Può essere fatto senza infrangere davvero nessuno dei requisiti, ma porta a comportamenti inaspettati. Le liste sono generalmente trattate come se il loro valore fosse derivato dai valori del loro contenuto, ad esempio quando si controlla la (non) uguaglianza. Molti si aspetterebbero - comprensibilmente - che tu possa usare qualsiasi elenco [1, 2]per ottenere la stessa chiave, dove dovresti mantenere esattamente lo stesso oggetto elenco. Ma la ricerca per valore si interrompe non appena un elenco utilizzato come chiave viene modificato e per la ricerca per identità richiede di mantenere esattamente lo stesso elenco, il che non richiede per nessun'altra operazione di elenco comune (almeno nessuna a cui riesco a pensare ).

Altri oggetti come i moduli e objectfanno comunque un affare molto più grande dalla loro identità di oggetto (quando è stata l'ultima volta che hai chiamato due oggetti modulo distinti sys?), E vengono comunque confrontati da quello. Pertanto, è meno sorprendente, o addirittura previsto, che, quando vengono utilizzati come chiavi di comando, anche in questo caso si confrontino per identità.


31

Perché non posso usare un elenco come chiave di comando in Python?

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

(per chiunque si imbatta in questa domanda cercando un modo per aggirarla)

come spiegato da altri qui, davvero non puoi. Puoi comunque usare la sua rappresentazione di stringa se vuoi davvero usare la tua lista.


5
Mi dispiace, non vedo davvero il tuo punto. Non è diverso dall'uso di stringhe letterali come chiavi.
wim

11
Vero; Ho appena visto così tante risposte che spiegano effettivamente perché non è possibile utilizzare gli elenchi in termini di "la chiave deve essere modificabile", il che è così vero che volevo suggerire un modo per aggirarlo, nel caso in cui qualcuno (nuovo) lo cerchi ...
Remi

5
Perché non convertire semplicemente l'elenco in una tupla? Perché convertirlo in una stringa? Se usi una tupla, funzionerà correttamente con le classi che hanno un metodo di confronto personalizzato __eq__. Ma se li converti in stringhe, tutto viene confrontato dalla sua rappresentazione di stringa.
Aran-Fey

buon punto @ Aran-Fey. Assicurati solo che qualsiasi elemento nella tupla sia esso stesso hash. es. tupla ([[1,2], [2,3]]) come chiave non funzionerà perché gli elementi della tupla sono ancora liste.
Remi

17

Appena scoperto puoi cambiare List in tupla, quindi usarlo come chiavi.

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

15

Il problema è che le tuple sono immutabili e le liste no. Considera quanto segue

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

Cosa dovrebbe d[li]restituire? È lo stesso elenco? Che ne dici d[[1,2,3]]? Ha gli stessi valori, ma è un elenco diverso?

In definitiva, non esiste una risposta soddisfacente. Ad esempio, se l'unica chiave che funziona è la chiave originale, se non hai alcun riferimento a quella chiave, non potrai mai più accedere al valore. Con ogni altra chiave consentita, puoi costruire una chiave senza un riferimento all'originale.

Se entrambi i miei suggerimenti funzionano, allora hai chiavi molto diverse che restituiscono lo stesso valore, il che è più che un po 'sorprendente. Se solo il contenuto originale funziona, la tua chiave andrà rapidamente male, poiché gli elenchi sono fatti per essere modificati.


Sì, è lo stesso elenco quindi mi aspetto d[li]che rimanga 5. d[[1,2,3]]fare riferimento a un oggetto elenco diverso come chiave, quindi sarebbe un KeyError. Non vedo ancora alcun problema ... tranne che lasciare che una chiave venga raccolta in modo spazzatura potrebbe rendere inaccessibili alcuni valori di dict. Ma questo è un problema pratico, non logico ..
wim

@wim: d[list(li)]essere un KeyError fa parte del problema. In quasi tutti gli altri casi d'uso , lisarebbe indistinguibile da un nuovo elenco con contenuti identici. Funziona, ma per molti è controintuitivo. Inoltre, quando è stata l'ultima volta che hai davvero dovuto usare un elenco come chiave di comando? L'unico caso d'uso che posso immaginare è quando stai comunque hashing tutto in base all'identità, e in quel caso dovresti farlo invece di fare affidamento su __hash__ed __eq__essere basato sull'identità.

@delnan Il problema è semplicemente che non sarebbe molto utile dict a causa di tali complicazioni? o c'è qualche motivo per cui potrebbe effettivamente rompere un dict?
wim

1
@wim: quest'ultimo. Come affermato nella mia risposta, in realtà non infrange i requisiti sulle chiavi dict, ma è probabile che introduca più problemi di quanti ne risolva.

1
@delnan - volevi dire "il primo"
Jason

9

Ecco una risposta http://wiki.python.org/moin/DictionaryKeys

Cosa andrebbe storto se provassi a utilizzare gli elenchi come chiavi, con l'hash come, ad esempio, la loro posizione di memoria?

Cercare elenchi diversi con lo stesso contenuto produrrebbe risultati diversi, anche se il confronto di elenchi con lo stesso contenuto li indicherebbe come equivalenti.

Che dire dell'utilizzo di un elenco letterale in una ricerca nel dizionario?


3

Il tuo tendalino può essere trovato qui:

Perché gli elenchi non possono essere chiavi del dizionario

I nuovi arrivati ​​a Python spesso si chiedono perché, mentre il linguaggio include sia una tupla che un tipo di lista, le tuple sono utilizzabili come chiavi di dizionario, mentre le liste no. Questa è stata una decisione progettuale deliberata e può essere meglio spiegata comprendendo prima come funzionano i dizionari Python.

Fonte e ulteriori informazioni: http://wiki.python.org/moin/DictionaryKeys


3

Poiché gli elenchi sono modificabili, le dictchiavi (e i setmembri) devono essere modificabili e l'hashing di oggetti modificabili è una cattiva idea perché i valori hash dovrebbero essere calcolati sulla base degli attributi dell'istanza.

In questa risposta, fornirò alcuni esempi concreti, auspicabilmente aggiungendo valore alle risposte esistenti. Ogni intuizione si applica anche agli elementi della struttura setdati.

Esempio 1 : hashing di un oggetto modificabile in cui il valore hash è basato su una caratteristica mutabile dell'oggetto.

>>> 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

Dopo la modifica stupid, non può più essere trovato nel dict perché l'hash è cambiato. Trova solo una scansione lineare sull'elenco delle chiavi del dict stupid.

Esempio 2 : ... ma perché non solo un valore hash costante?

>>> 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

Anche questa non è una buona idea perché gli oggetti uguali dovrebbero essere hash in modo identico in modo da poterli trovare in un dicto set.

Esempio 3 : ... ok, che dire degli hash costanti in tutte le istanze ?!

>>> 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

Le cose sembrano funzionare come previsto, ma pensa a cosa sta succedendo: quando tutte le istanze della tua classe producono lo stesso valore hash, avrai una collisione hash ogni volta che ci sono più di due istanze come chiavi in ​​a dicto presenti in a set.

Trovare l'istanza giusta con my_dict[key]o key in my_dict(o item in my_set) deve eseguire tanti controlli di uguaglianza quante sono le istanze di stupidlist3nelle chiavi del dict (nel caso peggiore). A questo punto, lo scopo del dizionario - ricerca O (1) - è completamente sconfitto. Ciò è dimostrato nelle seguenti tempistiche (eseguite con IPython).

Alcuni tempi per l'esempio 3

>>> 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)

Come puoi vedere, il test di appartenenza nel nostro stupidlists_setè ancora più lento di una scansione lineare nel suo complesso lists_list, mentre hai il tempo di ricerca super veloce previsto (fattore 500) in un set senza un sacco di collisioni hash.


TL; DR: puoi usare tuple(yourlist)come dictchiavi, perché le tuple sono immutabili e hash.


>>> x = (1,2,3321321321321,) >>> id (x) 139936535758888 >>> z = (1,2,3321321321321,) >>> id (z) 139936535760544 >>> id ((1, 2,3321321321321,)) 139936535810768 Questi 3 hanno gli stessi valori di tupla ma un id diverso. Quindi un dizionario con la chiave x non avrà alcun valore per la chiave z?
Ashwani

@Ashwani l'hai provato?
timgeb

Sì, funziona come previsto, il mio dubbio è che tutte le tuple con gli stessi valori hanno ID diversi. Quindi su quale base viene calcolato questo hash?
Ashwani

@Ashwani L'hash di xed zè lo stesso. Se qualcosa non è chiaro, apri una nuova domanda.
timgeb

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

1

La semplice risposta alla tua domanda è che l'elenco delle classi non implementa l' hash del metodo che è richiesto per qualsiasi oggetto che desideri essere utilizzato come chiave in un dizionario. Tuttavia il motivo per cui l' hash non è implementato nello stesso modo in cui è, ad esempio, la classe tupla (basata sul contenuto del contenitore) è perché una listaèmutabile quindi la modifica della lista richiederebbe il ricalcolo dell'hash che potrebbe significare l'elenco in ora si trova nel bucket sbagliato all'interno della tabella hash sottostante. Notare che poiché non è possibile modificare una tupla (immutabile) non si verifica questo problema.

Come nota a margine, l'effettiva implementazione della ricerca di dictobjects si basa sull'algoritmo D di Knuth Vol. 3, Sez. 6.4. Se hai quel libro a tua disposizione, potrebbe essere una lettura utile, inoltre se sei davvero, davvero interessato potresti dare un'occhiata ai commenti degli sviluppatori sull'effettiva implementazione di dictobject qui. Entra nei dettagli su come funziona esattamente. C'è anche una lezione su Python sull'implementazione di dizionari che potrebbero interessarti. Passano attraverso la definizione di una chiave e cosa è un hash nei primi minuti.


-1

Secondo la documentazione di Python 2.7.2:

Un oggetto è hash se ha un valore hash che non cambia mai durante la sua vita (necessita di un metodo hash ()) e può essere confrontato con altri oggetti (necessita di un metodo eq () o cmp ()). Gli oggetti hashable che confrontano uguale devono avere lo stesso valore hash.

Hashability rende un oggetto utilizzabile come chiave di dizionario e membro di un set, poiché queste strutture di dati utilizzano internamente il valore hash.

Tutti gli oggetti incorporati immutabili di Python sono hashable, mentre nessun contenitore mutabile (come elenchi o dizionari) lo è. Gli oggetti che sono istanze di classi definite dall'utente sono hash per impostazione predefinita; si confrontano tutti in modo disuguale e il loro valore hash è il loro id ().

Una tupla è immutabile nel senso che non è possibile aggiungere, rimuovere o sostituire i suoi elementi, ma gli elementi stessi possono essere mutabili. Il valore hash dell'elenco dipende dai valori hash dei suoi elementi, quindi cambia quando si modificano gli elementi.

L'uso degli ID per gli hash delle liste implicherebbe che tutte le liste si confrontino in modo diverso, il che sarebbe sorprendente e scomodo.


1
Questo non risponde alla domanda, vero? hash = idnon rompe l'invariante alla fine del primo paragrafo, la domanda è perché non è fatto in quel modo.

@delnan: ho aggiunto l'ultimo paragrafo per chiarire.
Nicola Musatti

-1

Un dizionario è una HashMap che memorizza la mappa delle tue chiavi, il valore convertito in una nuova chiave con hash e la mappatura del valore.

qualcosa come (codice psuedo):

{key : val}  
hash(key) = val

Se ti stai chiedendo quali sono le opzioni disponibili che possono essere utilizzate come chiave per il tuo dizionario. Poi

tutto ciò che è hash (può essere convertito in hash e mantiene un valore statico cioè immutabile in modo da creare una chiave con hash come indicato sopra) è idoneo ma poiché gli oggetti elenco o set possono essere modificati in movimento, quindi anche hash (chiave) dovrebbe aver bisogno variare solo per essere sincronizzato con il tuo elenco o set.

Puoi provare :

hash(<your key here>)

Se funziona bene, può essere usato come chiave per il tuo dizionario oppure convertirlo in qualcosa di hashable.


In breve :

  1. Converti quella lista in tuple(<your list>).
  2. Converti quella lista in str(<your list>).

-1

dictle chiavi devono essere modificabili. Le liste sono modificabili e non forniscono un metodo hash valido .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.