2015-11-30 5 views
22

Sto cercando di ottenere l'hash di una funzione lambda. Perché ottengo due valori (8746164008739 e -9223363290690767077)? Perché l'hash della funzione lambda non ha sempre un valore?Hash per la funzione lambda in Python

>>> fn = lambda: 1 
>>> hash(fn) 
-9223363290690767077 
>>> fn = lambda: 1 
>>> hash(fn) 
8746164008739 
>>> fn = lambda: 1 
>>> hash(fn) 
-9223363290690767077 
>>> fn = lambda: 1 
>>> hash(fn) 
8746164008739 
>>> fn = lambda: 1 
>>> hash(fn) 
-9223363290690767077 
+3

Giusto per essere chiari, ci si rende conto che si sta tagliando l'oggetto della funzione stessa, non si sta tagliando il valore che restituirebbe quando chiamato, giusto? –

+2

Probabilmente è una cattiva idea, ma potresti semplicemente considerare l'oggetto codice: 'hash ((lambda: 1) .__ code __)' – berdario

+0

solo curioso ... a cosa servono i modelli per cui avresti bisogno di questa funzionalità? frequenti le cose nei dizionari in base a una funzione arbitraria? –

risposta

36

Due oggetti non sono garantiti per l'hash allo stesso valore a meno che non siano uguali [1].

funzioni Python (compresi lambda) non risultano uguali, anche se hanno codice identico [2]. Ad esempio:

>>> (lambda: 1) == (lambda: 1) 
False 

Attuazione-saggio, questo comportamento è dovuto al fatto che gli oggetti funzionali non forniscono il proprio operatore di uguaglianza. Invece, ereditano quello predefinito che utilizza l'identità dell'oggetto, cioè il suo indirizzo. Dal documentation:

Se non è definita alcuna __cmp__(), __eq__() o __ne__() operazione, classe istanze vengono confrontati per identità di oggetto (“indirizzo”).

Ecco ciò che accade nel vostro esempio particolare:

fn = lambda: 1 # New function is allocated at address A and stored in fn. 
fn = lambda: 1 # New function is allocated at address B and stored in fn. 
       # The function at address A is garbage collected. 
fn = lambda: 1 # New function is allocated at address A and stored in fn. 
       # The function at address B is garbage collected. 
fn = lambda: 1 # New function is allocated at address B and stored in fn. 
       # The function at address A is garbage collected. 
... 

Dato indirizzo A è sempre hash per un valore, e l'indirizzo B ad un altro, si sta vedendo hash(fn) si alternano tra i due valori. Questo comportamento alternato è, tuttavia, un artefatto di implementazione e potrebbe cambiare un giorno se, ad esempio, il garbage collector si comportasse in modo leggermente diverso.

La seguente nota penetranti è stato contributi di @ruakh:

Vale la pena notare che non è possibile scrivere un processo generale per determinare se due funzioni sono equivalenti. (Questa è una conseguenza della undecidability del halting problem.) Inoltre, due funzioni Python possono comportano in modo diverso, anche se il loro codice è identico (poiché possono essere riferiti a chiusure variabili distinte-but-nomi identici). Quindi è logico che le funzioni di Python di non sovraccarichino l'operatore di uguaglianza: non c'è modo di implementare qualcosa di meglio del confronto di identità di oggetto predefinito .

[1] L'inverso non è generalmente vero: due oggetti che si confrontano in modo non uguale possono avere lo stesso valore di hash. Questo è chiamato hash collision.

[2] Calling tuoi lambda e quindi hashing il risultato sarebbe ovviamente sempre dare lo stesso valore in quanto hash(1) è sempre lo stesso all'interno di un programma:

>>> (lambda: 1)() == (lambda: 1)() 
True 
+0

Informazioni sulla prima frase, non confrontare gli oggetti uguali * se * i loro hash sono uguali? – JulienD

+6

@muraveill: No, potrebbe esserci una collisione di hash. –

+1

@muraveill La confusione a riguardo potrebbe essere dovuta al fraseggio.Il fraseggio è corretto, ma il fraseggio può avere più senso come parte di un'istruzione composta: "Se due oggetti sono uguali, allora i loro hash sono garantiti uguali (secondo la specifica di' hash() '). Se due oggetti sono non uguale, quindi i loro hash possono o non possono essere uguali. " –

10

L'hash di un oggetto lambda funzione è in base al suo indirizzo di memoria (in CPython questo è ciò che restituisce la funzione id). Ciò significa che qualsiasi oggetto a due funzioni avrà hash diversi (supponendo che non vi siano collisioni hash), anche se le funzioni contengono lo stesso codice.

Per spiegare cosa sta succedendo nella domanda, innanzitutto notare che scrivere fn = lambda: 1 crea un nuovo oggetto funzione in memoria e associa il nome a fn. Questa nuova funzione avrà quindi un diverso valore hash per qualsiasi funzione esistente.

Ripetendo fn = lambda: 1, si ottiene valori per gli hash alternata perché quando fn è legato all'oggetto nuova funzione creato , la funzione che fnprecedenza puntato è garbage collection da Python. Questo perché non ci sono più riferimenti ad esso (dal momento che il nome fn ora punta a un oggetto diverso).

L'interprete Python riutilizza quindi questo vecchio indirizzo di memoria per il prossimo nuovo oggetto funzione creato scrivendo fn = lambda: 1.

Questo comportamento può variare tra diversi sistemi e implementazioni Python.

5

Ogni volta che si esegue fn = lambda: 1, viene creato un nuovo oggetto funzione e il vecchio oggetto associato al nome fn viene contrassegnato per l'eliminazione. Ma Python non si limita a deallocare l'oggetto, passando la sua memoria al sistema operativo. Per minimizzare le chiamate di sistema per l'allocazione della memoria e per ridurre al minimo la frammentazione della memoria, Python tenta di riciclare la memoria quando può. E così quando crei fn = lambda: 1 una terza volta l'interprete nota che ha un blocco di RAM a portata di mano che è abbastanza grande per il nuovo oggetto funzione, e quindi usa quel blocco. E così il tuo terzo fn finisce in quel blocco di RAM e quindi ha lo stesso id del primo fn, poiché l'id degli oggetti CPython è il loro indirizzo di memoria.

(Come altri hanno già detto l'hash di qualsiasi tipo di oggetto che non fornisce una specifica implementazione di __hash__ si riferiscono al suo id CPython. E se una classe non definisce un metodo __cmp__ o __eq__ non dovrebbe definire un Operazione __hash__).

4

Decidere se due funzioni sono uguali è impossibile, in quanto è un superset del problema di interruzione.

In un mondo ideale, il confronto (e quindi l'hashing) delle funzioni comporterebbe un errore di tipo. A quanto pare a Python questo non piace, e invece sceglie di usare l'identità delle funzioni per confrontarle (e quindi cancellarle).