2010-09-13 2 views
4

J'ai une situation étrange. J'ai un dict, self.containing_dict. En utilisant la sonde de débogage, je vois le contenu de dict et je peux voir que self est une clé de celui-ci. Mais regardez ceci:Python: `key pas dans my_dict` mais` key dans my_dict.keys() `

>>> self in self.containing_dict 
False 
>>> self in self.containing_dict.keys() 
True 
>>> self.containing_dict.has_key(self) 
False 

Que se passe-t-il?

(je note que c'est un morceau de code qui est exécuté sur un rappel weakref.)

Mise à jour: on m'a demandé de montrer la mise en œuvre de __hash__self. Ici, il est:

def __hash__(self): 
    return hash(
     (
      tuple(sorted(tuple(self.args))), 
      self.star_args, 
      tuple(sorted(tuple(self.star_kwargs))) 
     ) 
    ) 

args = property(lambda self: dict(self.args_refs)) 

star_args = property(
    lambda self: 
     tuple((star_arg_ref() for star_arg_ref in self.star_args_refs)) 
) 

star_kwargs = property(lambda self: dict(self.star_kwargs_refs))  
+0

est-ce qu'il arrive d'implémenter son propre '__hash__'? –

+0

Etes-vous sûr que c'est vraiment un dict, et pas quelque chose avec juste la même interface? Qu'est-ce que 'self'? A-t-il un '__eq__' redéfini? –

+0

Est-ce que 'self.containing_dict' est juste un Python' dict' ou quelque chose comme un 'weakref.WeakKeyDictionary'? Si les premiers, sont les clés ou les valeurs du dictre eux-mêmes weakrefs? – llasram

Répondre

5

Le problème que vous décrivez ne peut être causée par self ayant mis en œuvre __eq__ (ou __cmp__) sans mettre en œuvre un __hash__ d'accompagnement. Si vous n'avez pas implémenté une méthode __hash__, vous devez le faire - normalement, vous ne pouvez pas utiliser les objets qui définissent __eq__ mais pas __hash__ comme clés dict, mais si vous héritez d'un __hash__ qui peut passer. Si vous implémentez __hash__, vous devez vous assurer qu'il agit correctement: le résultat ne doit pas changer pendant la durée de vie de l'objet (ou au moins tant que l'objet est utilisé en tant que clé ou ensemble de dict). item), et il doit être compatible avec __eq__. La valeur de hachage d'un objet doit être identique à (conformément à __eq__ ou __cmp__.) La valeur de hachage d'un objet peut être différente de des objets, mais ce n'est pas forcément le cas. Les exigences signifient également que vous ne pouvez pas modifier le résultat de __eq__ sur la durée de vie de l'objet, ce qui explique pourquoi les objets mutables ne peuvent généralement pas être utilisés comme clés dict.

Si votre __hash__ et __eq__ ne sont pas apparié, Python ne sera pas en mesure de trouver l'objet dans dicts et ensembles, mais il montrera encore dans dict.keys() et list(set), qui est ce que vous décrivez ici. La méthode habituelle pour implémenter les méthodes __hash__ consiste à renvoyer le hash() de tous les attributs que vous utilisez dans votre méthode __eq__ ou __cmp__.

+0

J'appelle la verrue! Le construit dans 'object' définit un' __hash__', donc si vous faites la bonne chose et créez une nouvelle classe de style personnalisée ('X'), puis surchargez' __eq__', Python vous laissera heureusement utiliser votre objet comme une clé dict , tout en raccourcissant à la base 'object .__ hash__' pour le cas OP: ' X() dans {X(): None} 'Ugh. – EoghanM

+1

Ce n'est pas tellement une verrue comme un * bug *, dans Python 2.x. Un bug introduit lors de l'introduction de classes de style nouveau et qui est passé inaperçu pendant longtemps. C'est corrigé dans Python 3. –

0

Vous avez probablement un hachage personnalisé et une comparaison définie pour une classe self est une instance et vous avez muté self après l'avoir ajouté au dictionnaire.

Si vous utilisez un objet mutable en tant que clé de dictionnaire, il se peut que vous ne puissiez pas accéder au dictionnaire après le muter, mais il apparaîtra toujours dans le résultat keys().

2

A en juger par votre méthode __hash__, la classe stocke des références à ses arguments et l'utilise comme un hachage. Le problème est que ces arguments sont partagés avec le code qui a construit l'objet. Si elles changent l'argument, le hachage changera et vous ne pourrez pas trouver l'objet dans les dictionnaires dans lesquels il a été.

Les arguments ne doivent pas être compliqués, juste une simple liste suffira.

In [13]: class Spam(object) : 
    ....:  def __init__(self, arg) : 
    ....:   self.arg = arg 
    ....:  def __hash__(self) : 
    ....:   return hash(tuple(self.arg,)) 

In [18]: l = range(5) 

In [19]: spam = Spam(l) 

In [20]: hash(spam) 
Out[20]: -3958796579502723947 

Si je change la liste que j'ai passé en argument, le hachage va changer.

In [21]: l += [10] 

In [22]: hash(spam) 
Out[22]: -6439366262097674983 

Depuis les clés du dictionnaire sont organisées par hachage, quand je fais x in d, la première chose que Python n'est calculer le hachage de x, et regardez dans le dictionnaire pour quelque chose avec cette valeur de hachage. Le problème est que lorsque le hachage d'un objet change après avoir été placé dans le dictionnaire, Python regarde la nouvelle valeur de hachage, et ne voit pas la clé désirée. En utilisant la liste des clés, force Python à vérifier chaque clé par l'égalité, sans passer par le contrôle de hachage.