A.B.
A.B.

Reputation: 89

How to have different objects from different classes in one dict in Python?

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.

Upvotes: 0

Views: 878

Answers (3)

John Strood
John Strood

Reputation: 2039

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.

Upvotes: 1

Brian McCutchon
Brian McCutchon

Reputation: 8594

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.

Upvotes: 2

Jo&#227;o Haas
Jo&#227;o Haas

Reputation: 2139

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

Upvotes: 1

Related Questions