0

I had a question about dictionaries with custom objects. In a dict, I know that the key has to be immutable, so if I want to use a custom class, I have to define its hash function. The hash doc in python recommends you use the hash function on the tuple of the equal dunder method. So for example, i defined the custom class temp as such:

class temp():
    def __init__(self,value):
        self.value = value
    def __hash__(self):
        return hash(self.value)
    def __eq__(self,other):
        return self.value == other.value
    def __lt__(self,other):
        return self.value < other.value

This way I can have they key:value pair such as temp(1):1. So to my question. In python, you can have different types in the same dict. So I declared this dict:

myDict={ temp(1):1, temp(2):2, 'a':1,1:1, (1,2):1, True:1 }

The problem I am facing is that I would get an error for the int:int and bool:int pairing telling me the error:

'bool' object has no attribute 'value'

or

'int' object has no attribute 'value'

Can someone explain to me why this is the case? The same issue would happen if I have a different class in the dict as well. So an object from a cars class would give this error:

'cars' object has no attribute 'value'

Strangely enough in my tests, I found that if the key is a tuple or a float, it works fine. Any help would be greatly appreciated. I wanted to know why the error is happening and how I can fix it. MY main goal is to learn how to my one dict that has various objects from different classes.

3 Answers 3

2

Your eq method needs to check if the other object is the same type:

def __eq__(self,other):
    if not isinstance(other, temp):
        return NotImplemented
    return self.value==other.value

That said, I highly recommend using dataclasses for cases like this. They define init, eq, and (if frozen=True) hash for you, which helps avoid this sort of issue.

Sign up to request clarification or add additional context in comments.

2 Comments

From the code available and the explanation given, it seems like temp is supposed to work as a wrapper to generic values, meaning you don't necessarily want to compare with only objects of the same class. The best option would probably change the return NotImplemented to return self.value == other. This way the class is able to compare with other values, and with itself.
Thank you for your response. Quick question, should I modify the lt method as well to take into account of it being an instance or not? Also, why did you change your code from the original ``` return isinstance(other,temp) and self.value==other.value ``` I understood why you returned that code, but I don't see why the need for the change. Was there a problem with your original code?
1

You can define your __eq__ method like this:

def __eq__(self, other):
    if other is None:
        return False

    if self.__class__ != other.__class__:
        return False

    return self.value == other.value

Strangely enough in my tests, I found that if the key is a tuple or a float, it works fine.

As for the second question, this has got to do with how a dict works. For every key, the instance of dict checks if the hash of the key exists. If yes, then it checks for equality with other keys with the same hash. Here, the check for equality is to check if they are basically the same keys (and hence the same hash). If the equality check fails, then the keys are deemed different.

If there are no hash collisions, then no equality checks are done. Hence, when you used a tuple as a key, say, (1, 2), its hash((1, 2)) = 3713081631934410656, which doesn't yet exist in the dict. Hence no error.

2 Comments

Should I also compare the class type in the lt method as well?
@A.B. In your example of a dict, it isn't necessary. When you are performing something like sorting, these comparision methods are used. You definitely could throw an exception when different classes are compared. While it makes sense to say two instances of dissimilar classes are always unequal, you may never want to compare <, >, <=, '>=` for them.
1

The issue happens when running the __eq__ and __lt__ dunder methods. You can reproduce the same by running:

temp(1) == 1

The issue happens because __eq__ receives other as 1, and the value 1 does not have a .value, but you're trying to use it here:

return self.value == other.value

If you just use other for comparisons it should work:

class temp():
    def __init__(self,value):
        self.value = value
    def __hash__(self):
        return hash(self.value)
    def __eq__(self,other):
        return self.value == other
    def __lt__(self,other):
        return self.value < other

1 Comment

Thank you for the explanation! Now I know how the hash & equal comes to play. Just to mention, the code you provided doesn't work because the comparison would give errors and treat some objects the same when they are not. For instance, the dict: ``` x={bob(1):1,2:2} ``` bob(2) would evaluate to True when I would want/think it will evaluate to False. To fix this, I would still compare value attribute, but also check they are the same type.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.