简体   繁体   中英

python mutable and hashable types

Can anyone please explain how is it possible to get an object which is both hashable and mutable?

I have seen: Hashable, immutable it does not answer my question

I heard it is possible in python.

Here is some code that shows you the effects of making an object both hashable and mutable. Note that the link you provided does actually answer your question in Andrew Jaffe's answer and the comments under it; I've added some code from this question about hashing in order to help explain.

The default for a python object's hash value is the object ID, which will not change during it's lifetime. A custom value can be provided by __hash__ ; however, in order to be useful, this must be translated into something that can be hashable, such as an integer or a string.

class test():
    use_int = 0
    use_name = ""
    use_list = []

    def __init__(self, use_int:int, use_name:str, use_list:list)->None:
        self.use_int = use_int
        self.use_name = use_name
        self.use_list = use_list
    
    # Compact the attributes into a tuple of int and strings
    # Without changing the list into a string, the hash will fail
    def __key(self):
        return (str(self.use_int), self.use_name,",".join(self.use_list))
    
    # The above step could be done here with a small object like this    
    def __hash__(self):
        return hash(self.__key())
    
    # For fun: try changing this to "__repr__"
    def __str__(self):
        return ",".join(self.__key())

Let's run this through and see what the outcomes are:

      
if __name__ == "__main__":
    # Initialise our object
    test_obj = test(0,"John",["test","more test",])

Anytime we want to look at the hash values, we can use print(test_obj.__hash__()) . Try changing the int and seeing if the hash changes. Also, since Python uses a random salt with str hashes to prevent collisions, note also that hashing this way will supply different hash values in different processes.

We can demonstrate that the object is usable as a hashable object by testing if a dictionary will accept the object as a key. Dictionary keys cannot be lists, for example.

    test_dict = dict()
    test_dict[test_obj] = "first object used as key"
    print(test_dict)

Try changing the list within the object, and seeing if it is still acceptable:

    test_obj.use_list.append("yet more tests")
    test_dict[test_obj] = "second object used as key"
    print(test_dict)

What if we need to go back?

    del test_obj.use_list[-1]
    test_dict[test_obj] = "third object used as key"
    print(test_dict)

Note how "first object" has been changed to "third object".

Putting all of this code together:

class test():
    
    use_int = 0
    use_name = ""
    use_list = []
    
    def __init__(self, use_int:int, use_name:str, use_list:list)->None:
        self.use_int = use_int
        self.use_name = use_name
        self.use_list = use_list
    
    def __key(self):
        return (str(self.use_int), self.use_name,",".join(self.use_list))
        
    def __hash__(self):
        return hash(self.__key())
    
    def __str__(self):
        return ",".join(self.__key())
        
if __name__ == "__main__":
    test_obj = test(0,"John",["test","more test",])
    print(test_obj.__hash__())
    test_obj.use_int = 1
    print(test_obj.__hash__())
    test_obj.use_int = 2
    print(test_obj.__hash__())
    test_dict = dict()
    test_dict[test_obj] = "object used as key"
    print(test_dict)
    test_obj.use_list.append("yet more tests")
    test_dict[test_obj] = "second object"
    print(test_dict)
    del test_obj.use_list[-1]
    test_dict[test_obj] = "third object"
    print(test_dict)
    print(test_obj)
    test_obj.use_int = 1
    print(test_obj.__hash__())

But what if we need to have a consistent, predictable hash value? __hash() doesn't have to use hash() . It can return other values. This would mean making the process compatible though - otherwise you'll get TypeError: __hash__ method should return an integer .

Try converting the name into an integer:

    def __key(self):
        name_number = 0
        for c in self.use_name:
            name_number += ord(c)
        return self.use_int + name_number

    def __hash__(self):
        return self.__key()
    
    def __str__(self):
        return str(self.__key())

What happens if you run the dictionary tests in this scenario?

You'll notice that instead of having two entries to the dictionary, you only get one - this is because changing the list does not change the hash value produced by the object.

Outcome of the original random hash dict tests:

{< main .test object at 0x7f05bc1f1fd0>: 'first object'}
{< main .test object at 0x7f05bc1f1fd0>: 'object used as key', < main .test object at 0x7f05bc1f1fd0>: 'second object'}
{< main .test object at 0x7f05bc1f1fd0>: 'third object', < main .test object at 0x7f05bc1f1fd0>: 'second object'}

Outcome of the second fixed hash dict tests:

{< main .test object at 0x7fc7b5510fd0>: 'first object'}
{< main .test object at 0x7fc7b5510fd0>: 'second object'}
{< main .test object at 0x7fc7b5510fd0>: 'third object'}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM