with Hash pour la fonction lambda en Python




python lambda value (4)

Deux objets ne sont pas garantis pour le hachage à la même valeur, à moins qu'ils ne se comparent égaux [1].

Les fonctions Python (y compris les lambdas) ne se comparent pas même si elles ont un code identique [2]. Par exemple:

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

Du point de vue de l'implémentation, ce comportement est dû au fait que les objets fonction ne fournissent pas leur propre opérateur d'égalité. Au lieu de cela, ils héritent de celui par défaut qui utilise l'identité de l'objet, c'est-à-dire son adresse. De la documentation :

Si aucune opération __cmp__() , __eq__() ou __ne__() n'est définie, les instances de classe sont comparées par identité d'objet ("adresse").

Voici ce qui se passe dans votre exemple particulier:

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

Puisque l'adresse A est toujours hachée à une valeur et l'adresse B à une autre, vous voyez que le hash(fn) alterne entre les deux valeurs. Ce comportement en alternance est toutefois un artefact de mise en œuvre et pourrait changer un jour si, par exemple, le ramasse-miettes se comportait légèrement différemment.

La note perspicace suivante a été apportée par @ruakh:

Il est à noter qu'il n'est pas possible d'écrire un processus général pour déterminer si deux fonctions sont équivalentes. (Ceci est une conséquence de l’ undecidability du problème d’arrêt ). En outre, deux fonctions Python peuvent se comporter différemment même si leur code est identique (car il peut s’agir de fermetures se référant à des variables distinctes mais identifiées). Il est donc logique que les fonctions Python ne surchargent pas l'opérateur d'égalité: il n'y a aucun moyen de mettre en œuvre quelque chose de mieux que la comparaison d'identité d'objet par défaut.

[1] L'inverse n'est généralement pas vrai: deux objets qui se comparent inégaux peuvent avoir la même valeur de hachage. C'est ce qu'on appelle une collision de hachage .

[2] L' appel de vos lambdas et le hachage du résultat donneraient bien sûr toujours la même valeur puisque le hash(1) est toujours le même dans un programme:

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

J'essaie d'obtenir le hachage d'une fonction lambda. Pourquoi ai-je deux valeurs (8746164008739 et -9223363290690767077)? Pourquoi le hachage de la fonction lambda n’est-il pas toujours une valeur?

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

Il est impossible de décider si deux fonctions sont égales, car il s’agit d’un sur-ensemble du problème d’arrêt.

Dans un monde idéal, la comparaison (et donc le hachage) des fonctions entraînerait une erreur de type. Python n'aime apparemment pas cela, et choisit plutôt d'utiliser l'identité des fonctions pour les comparer (et donc les hacher).


Le hachage d'un objet fonction lambda est basé sur son adresse mémoire (dans CPython, c'est ce que la fonction id retourne). Cela signifie que deux objets de fonction auront des hachages différents (en supposant qu'il n'y ait pas de collision de hachage), même si les fonctions contiennent le même code.

Pour expliquer ce qui se passe dans la question, notez d’abord que l’écriture de fn = lambda: 1 crée un nouvel objet fonction en mémoire et lui associe le nom fn . Cette nouvelle fonction aura donc une valeur de hachage différente pour toutes les fonctions existantes.

En répétant fn = lambda: 1 , vous obtenez des valeurs alternées pour les hachages, car lorsque fn est lié à l’objet de la fonction nouvellement créée, la fonction que fn précédemment désignée est la mémoire collectée par Python. C'est parce qu'il n'y a plus de références à ce dernier (puisque le nom fn pointe maintenant vers un objet différent).

L'interpréteur Python réutilise alors cette ancienne adresse mémoire pour le nouvel objet fonction suivant créé en écrivant fn = lambda: 1 .

Ce comportement peut varier entre différents systèmes et implémentations Python.


Chaque fois que vous faites fn = lambda: 1 un nouvel objet fonction est créé et l'ancien objet lié au nom fn est marqué pour suppression. Mais Python ne se contente pas de désallouer l’objet, en transmettant sa mémoire au système d’exploitation. Pour minimiser les appels système à une allocation de mémoire et réduire la fragmentation de la mémoire, Python essaie de recycler la mémoire dès que possible. Et donc, lorsque vous créez fn = lambda: 1 une troisième fois, l'interpréteur remarque qu'il a un bloc de mémoire assez grand pour le nouvel objet fonction, et qu'il utilise donc ce bloc. Et donc votre 3ème fn se retrouve dans ce bloc de RAM et a donc le même identifiant que le premier fn , puisque l’identifiant des objets CPython est leur adresse mémoire.

(Comme d'autres l'ont mentionné, le hachage de tout type d'objet ne fournissant pas d'implémentation spécifique de __hash__ est basé sur son identifiant dans CPython. Et si une classe ne définit pas de méthode __cmp__ ou __eq__ , elle ne devrait pas non plus définir d'opération __hash__ ) .





lambda