首页 > 解决方案 > python可变和可散列类型

问题描述

谁能解释一下如何获得一个既可哈希又可变的对象?

我见过: Hashable, immutable它不能回答我的问题

我听说在 python 中是可能的。

标签: pythonpython-3.xmutablehashable

解决方案


下面是一些代码,向您展示了使对象既可哈希又可变的效果。请注意,您提供的链接确实在 Andrew Jaffe 的答案及其下方的评论中回答了您的问题;为了帮助解释,我从这个问题中添加了一些关于散列的代码。

python 对象的哈希值的默认值是对象 ID,在其生命周期内不会更改。自定义值可以由__hash__; 然而,为了有用,它必须被翻译成可以散列的东西,比如整数或字符串。

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())

让我们运行一下,看看结果是什么:

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

任何时候我们想查看哈希值,我们都可以使用print(test_obj.__hash__()). 尝试更改int并查看哈希是否更改。此外,由于 Python 使用带有 str 散列的随机盐来防止冲突,还要注意这种散列方式将在不同的进程中提供不同的散列值。

我们可以通过测试字典是否接受该对象作为键来证明该对象可用作可散列对象。例如,字典键不能是列表。

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

尝试更改对象中的列表,看看它是否仍然可以接受:

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

如果我们需要回去怎么办?

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

请注意“第一个对象”如何更改为“第三个对象”。

将所有这些代码放在一起:

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__())

但是如果我们需要一个一致的、可预测的哈希值呢?__hash()不必使用hash()!它可以返回其他值。这将意味着使过程兼容 - 否则你会得到TypeError: __hash__ method should return 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())

如果在这种情况下运行字典测试会发生什么?

您会注意到,字典中没有两个条目,而是只有一个 - 这是因为更改列表不会更改对象产生的哈希值。

原始随机哈希字典测试的结果:

{< main .test object at 0x7f05bc1f1fd0>: 'first object'}
{< main .test object at 0x7f05bc1f1fd0>: 'object used as key', < main .test object at 0x7f05bc1f1fd0>: 'second object'}
{< main . 0x7f05bc1f1fd0> 处的测试对象:'第三个对象',< 0x7f05bc1f1fd0 处的.test 对象>:'第二个对象'}

第二次固定哈希字典测试的结果:

{< main .test object at 0x7fc7b5510fd0>: 'first object'}
{< main .test object at 0x7fc7b5510fd0>: 'second object'}
{< main .test object at 0x7fc7b5510fd0>: '第三个 object'}


推荐阅读