Hash pour la fonction lambda en Python

j'essaie d'obtenir le hash d'une fonction lambda. Pourquoi j'obtiens deux valeurs (8746164008739 et -9223363290690767077)? Pourquoi le hachage de la fonction lambda 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
22
demandé sur Alex Riley 2015-11-30 15:22:33

4 réponses

deux objets ne sont pas garantis pour hachurer à la même valeur à moins qu'ils ne comparent égal [1].

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

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

du point de vue de la mise en œuvre, ce comportement est dû au fait que les objets de fonction ne fournissent pas leur propre opérateur d'égalité. Ils héritent plutôt de celui par défaut qui utilise l'identité de l'objet, c'est-à-dire son adresse. À partir de la documentation:

si non __cmp__(),__eq__() ou __ne__() opération définie, classe les cas sont comparés par l'identité de l'objet ("adresse").

Voici ce qui se passe dans votre exemple:

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

Depuis l'adresse A est toujours hashé à une valeur, et adresse B à l'autre, vous voyez hash(fn) alterner entre les deux valeurs. Ce comportement alternatif est cependant une mise en œuvre le collecteur d'ordures se comporte un peu différemment.

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

Il est intéressant de noter qu'il n'est pas possible d'écrire un processus général pour déterminer si deux fonctions sont équivalentes. (C'est un conséquence de l' indécidabilitéproblème de l'arrêt.) De plus, deux fonctions Python peuvent se comportent différemment même si leur le code est identique (puisqu'il peut s'agir de fermetures faisant référence à variables distinctes mais nommées de façon identique). Il est donc logique que Les fonctions Python ne surchargent pas l'opérateur égalité: il n'y a aucun moyen pour implémenter quoi que ce soit de mieux que l'objet-identité par défaut comparaison.

[1] l'inverse n'est généralement pas vrai: deux objets qui comparent unequal peuvent avoir la même valeur de hachage. Cela s'appelle un hash collision.

[2] appel vos lambdas et puis hashing le résultat donnerait bien sûr toujours la même valeur puisque hash(1) c'est toujours le même au sein d'un programme:

>>> (lambda: 1)() == (lambda: 1)()
True
37
répondu NPE 2015-12-01 14:49:45

le hachage d'un lambda l'objet de fonction est basé sur son adresse mémoire (en CPython c'est ce que le id renvoie la fonction). Cela signifie que deux objets de fonction auront des hachages différents (en supposant qu'il n'y ait pas de collisions 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 fn = lambda: 1 crée un nouvel objet de fonction en mémoire et lie le nom fn. Cette nouvelle fonction aura donc une autre valeur de hachage de toutes les fonctions existantes.

Répéter fn = lambda: 1, vous obtenez des valeurs alternées pour les hachures parce que quand fn est lié au nouvellement créé la fonction de l'objet, la fonction fndéjà a souligné garbage collecté par Python. C'est parce qu'il n'y a plus aucune référence à elle (puisque le nom fn renvoie maintenant à un objet différent).

l'interpréteur Python réutilise alors ce vieux adresse mémoire pour le prochain nouvel objet de fonction créé en écrivant fn = lambda: 1.

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

10
répondu Alex Riley 2015-11-30 18:05:26

Chaque fois que vous faites fn = lambda: 1 un nouvel objet de fonction est créé, et l'ancien objet lié au nom fn est marqué pour suppression. Mais Python ne désalloque pas simplement l'objet, en renvoyant sa mémoire à L'OS. Pour minimiser les appels système pour l'allocation de la mémoire et pour minimiser la fragmentation de la mémoire, Python essaie de recycler la mémoire quand il le peut. Et donc, lorsque vous créez fn = lambda: 1 une troisième fois que l'interpréteur remarque qu'il a un bloc de RAM à portée de main qui est assez grand pour le nouvel objet de fonction, et donc il utilise le bloc. Et ainsi votre 3ème!--1 -- > finit dans ce bloc de RAM et a donc le même id que le premier fn, puisque l'id des objets CPython est leur adresse mémoire.

(comme d'autres ont mentionné le hachage de n'importe quel type d'objet qui ne fournit pas une implémentation spécifique de __hash__ est basé sur son id en CPython. Et si une classe ne définit pas un __cmp__ ou __eq__ méthode, il ne devrait pas définir un __hash__ l'opération).

5
répondu PM 2Ring 2015-12-01 10:52:10

décider si deux fonctions sont égales est impossible, car c'est un super-ensemble du problème d'arrêt.

dans un monde idéal, la comparaison (et donc le hachage) des fonctions résulterait en 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).

4
répondu rightfold 2015-11-30 18:08:50