Cosa fa l'hash in Python?


88

Ho visto un esempio di codice in cui la hashfunzione è applicata a una tupla. Di conseguenza restituisce un numero intero negativo. Mi chiedo cosa fa questa funzione? Google non aiuta. Ho trovato una pagina che spiega come viene calcolato l'hash ma non spiega perché abbiamo bisogno di questa funzione.


8
Hai guardato i documenti ...
TerryA

vai a questo link (documentazione ufficiale). Specifica tutto. vai al link !
tailor_raj

2
Mi piace che la domanda non sia una ripetizione di "cos'è" ma un "perché ne abbiamo bisogno".
dnozay

il link ufficiale è molto confuso
Rasmi Ranjan Nayak

Risposte:


156

Un hash è un numero intero di dimensione fissa che identifica un valore particolare . Ogni valore deve avere il proprio hash, quindi per lo stesso valore otterrai lo stesso hash anche se non è lo stesso oggetto.

>>> hash("Look at me!")
4343814758193556824
>>> f = "Look at me!"
>>> hash(f)
4343814758193556824

I valori hash devono essere creati in modo tale che i valori risultanti siano distribuiti uniformemente per ridurre il numero di collisioni hash ottenute. Le collisioni hash si verificano quando due valori diversi hanno lo stesso hash. Pertanto, modifiche relativamente piccole spesso producono hash molto diversi.

>>> hash("Look at me!!")
6941904779894686356

Questi numeri sono molto utili, poiché consentono una rapida ricerca di valori in un'ampia raccolta di valori. Due esempi del loro utilizzo sono Python sete dict. In a list, se vuoi controllare se un valore è nell'elenco, con if x in values:, Python deve passare attraverso l'intero elenco e confrontare xcon ogni valore nell'elenco values. Questo può richiedere molto tempo per molto tempo list. In a set, Python tiene traccia di ogni hash e quando digiti if x in values:, Python otterrà il valore hash per x, lo cercherà in una struttura interna e quindi confronterà solo xcon i valori che hanno lo stesso hash di x.

La stessa metodologia viene utilizzata per la ricerca nel dizionario. Questo rende la ricerca in entrata sete dictmolto veloce, mentre la ricerca in entrata listè lenta. Significa anche che puoi avere oggetti non modificabili in a list, ma non in a seto come chiavi in ​​a dict. Il tipico esempio di oggetti non hashable è qualsiasi oggetto che è mutabile, il che significa che puoi cambiarne il valore. Se hai un oggetto modificabile, non dovrebbe essere hash, poiché il suo hash cambierà nel corso della sua durata, il che causerebbe molta confusione, poiché un oggetto potrebbe finire con il valore hash sbagliato in un dizionario.

Nota che l'hash di un valore deve essere lo stesso solo per un'esecuzione di Python. In Python 3.3 cambieranno infatti per ogni nuova esecuzione di Python:

$ /opt/python33/bin/python3
Python 3.3.2 (default, Jun 17 2013, 17:49:21) 
[GCC 4.6.3] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hash("foo")
1849024199686380661
>>> 
$ /opt/python33/bin/python3
Python 3.3.2 (default, Jun 17 2013, 17:49:21) 
[GCC 4.6.3] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hash("foo")
-7416743951976404299

Questo per rendere più difficile indovinare quale valore hash avrà una certa stringa, che è un'importante funzione di sicurezza per le applicazioni web, ecc.

I valori hash non dovrebbero quindi essere memorizzati in modo permanente. Se hai bisogno di usare i valori hash in modo permanente puoi dare un'occhiata ai tipi più "seri" di hash, funzioni di hash crittografiche , che possono essere usati per fare checksum verificabili di file ecc.


12
Riguardo a potenziali collisioni di hash: hash(-1) == hash(-2)(runnin Python 2.7)
Matthias

2
Sto eseguendo Python 3.6.1 e la collisione esiste.
The_Martian

hash(-1) == hash(-2)esiste ancora oggi. Fortunatamente, non influisce negativamente sul dizionario e sulle ricerche impostate. Tutti gli altri numeri interi si irisolvono da soli hash(i)eccetto -1.
Chris Conlan

36

TL; DR:

Fare riferimento al glossario : hash()viene utilizzato come scorciatoia per confrontare oggetti, un oggetto è considerato hash se può essere confrontato con altri oggetti. ecco perché usiamo hash(). E 'anche usato per l'accesso dicte setgli elementi che vengono implementati come tabelle hash ridimensionabili in CPython .

Considerazioni tecniche

  • di solito il confronto di oggetti (che può coinvolgere diversi livelli di ricorsione) è costoso.
  • preferibilmente, la hash()funzione è un ordine di grandezza (o più) meno costosa.
  • confrontare due hash è più facile che confrontare due oggetti, è qui che si trova il collegamento.

Se leggi come sono implementati i dizionari , usano tabelle hash, il che significa che derivare una chiave da un oggetto è una pietra angolare per il recupero di oggetti nei dizionari in formato O(1). Tuttavia, dipende molto dalla tua funzione hash per essere resistente alle collisioni . Il caso peggiore per ottenere un elemento in un dizionario è in realtà O(n).

In questa nota, gli oggetti modificabili di solito non sono modificabili. La proprietà hashable significa che puoi usare un oggetto come chiave. Se il valore hash viene utilizzato come chiave e il contenuto dello stesso oggetto cambia, cosa dovrebbe restituire la funzione hash? È la stessa chiave o un'altra? Esso dipende da come si definisce la funzione di hash.

Imparare con l'esempio:

Immagina di avere questa classe:

>>> class Person(object):
...     def __init__(self, name, ssn, address):
...         self.name = name
...         self.ssn = ssn
...         self.address = address
...     def __hash__(self):
...         return hash(self.ssn)
...     def __eq__(self, other):
...         return self.ssn == other.ssn
... 

Nota: tutto questo si basa sul presupposto che il SSN non cambia mai per un individuo (non so nemmeno dove verificare effettivamente quel fatto da fonte autorevole).

E abbiamo Bob:

>>> bob = Person('bob', '1111-222-333', None)

Bob va a vedere un giudice per cambiare il suo nome:

>>> jim = Person('jim bo', '1111-222-333', 'sf bay area')

Questo è quello che sappiamo:

>>> bob == jim
True

Ma questi sono due oggetti diversi con una memoria allocata diversa, proprio come due record diversi della stessa persona:

>>> bob is jim
False

Ora arriva la parte in cui hash () è utile:

>>> dmv_appointments = {}
>>> dmv_appointments[bob] = 'tomorrow'

Indovina un po:

>>> dmv_appointments[jim] #?
'tomorrow'

Da due record differenti è possibile accedere alle stesse informazioni. Ora prova questo:

>>> dmv_appointments[hash(jim)]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in __eq__
AttributeError: 'int' object has no attribute 'ssn'
>>> hash(jim) == hash(hash(jim))
True

Cosa è appena successo? È una collisione. Poiché hash(jim) == hash(hash(jim))sono entrambi numeri interi, dobbiamo confrontare l'input di __getitem__con tutti gli elementi che entrano in collisione. Il builtin intnon ha un ssnattributo quindi scatta.

>>> del Person.__eq__
>>> dmv_appointments[bob]
'tomorrow'
>>> dmv_appointments[jim]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: <__main__.Person object at 0x7f611bd37110>

In questo ultimo esempio, mostro che anche con una collisione, il confronto viene eseguito, gli oggetti non sono più uguali, il che significa che solleva con successo a KeyError.


Spiegazione davvero utile. Come principiante, questo mi ha aiutato a capire come creare classi che possono essere inserite in set e usarle come chiavi per dizionario / tabella hash. Inoltre, se eseguo collection [hashable_obj] = hashable_obj, potrei ottenere un puntatore a quell'istanza in seguito. Ma dimmi se c'è un modo migliore per tenere traccia di tali raccolte.
PaulDong

@dnozay Ma, comunque, l'output di hash()è un numero intero di dimensioni fisse, che può causare collisioni
sovrascambio

2
Qualcuno può approfondire l'uso di __eq__nell'esempio sopra. Viene chiamato dal dizionario quando cerca di confrontare la chiave che riceve con tutte le chiavi che ha? Tale che con delil __eq__metodo nell'ultimo esempio, il dizionario non ha nulla da chiamare da usare per determinare l'equivalenza della chiave che ha ricevuto con le chiavi che ha?
Jet Blue

1
@JetBlue La spiegazione della "collosione" è incompleta nell'esempio con la chiave hash(jim). Person.__eq__viene chiamato perché la chiave esistente ha lo stesso hash hash(jim)per garantire che Person.__eq__venga utilizzata la chiave giusta . Sbaglia perché presume che other, cioè int, abbia un ssnattributo. Se la hash(jim)chiave non esistesse nel dizionario __eq__non verrebbe chiamata. Questo spiega quando la ricerca della chiave può essere O (n): quando tutti gli elementi hanno lo stesso hash __eq__deve essere usato su tutti, ad esempio nel caso in cui la chiave non esiste.
WloHu

1
Sebbene comprenda l'interesse pedagogico del tuo esempio, non sarebbe più semplice scrivere dmv_appointments[bob.ssn] = 'tomorrow', eliminando la necessità di definire un __hash__metodo? Capisco che aggiunge 4 caratteri per ogni appuntamento che scrivi e leggi, ma mi sembra più chiaro.
Alexis

3

La documentazione dihash() Python per lo stato:

I valori hash sono numeri interi. Vengono utilizzati per confrontare rapidamente le chiavi del dizionario durante una ricerca nel dizionario.

I dizionari Python sono implementati come tabelle hash. Quindi ogni volta che usi un dizionario, hash()viene richiamata la chiave che ti passa per assegnazione, o ricerca.

Inoltre, i documenti per lodict stato del tipo :

I valori che non sono modificabili , ovvero i valori contenenti elenchi, dizionari o altri tipi modificabili (che vengono confrontati in base al valore anziché all'identità dell'oggetto) non possono essere utilizzati come chiavi.


1

L'hash viene utilizzato dai dizionari e dai set per cercare rapidamente l'oggetto. Un buon punto di partenza è l'articolo di Wikipedia sulle tabelle hash .


-2

Puoi usare il Dictionarytipo di dati in python. È molto molto simile all'hash e supporta anche l'annidamento, simile all'hash annidato.

Esempio:

dict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}
dict['Age'] = 8; # update existing entry
dict['School'] = "DPS School" # Add new entry

print ("dict['Age']: ", dict['Age'])
print ("dict['School']: ", dict['School'])

Per ulteriori informazioni, fare riferimento a questo tutorial sul tipo di dati del dizionario .

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.