Python 可变和可哈希类型

python mutable and hashable types

提问人:IamMashed 提问时间:4/25/2021 更新时间:4/25/2021 访问量:493

问:

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

我看过:可散列,不可变,它不能回答我的问题

我听说这在 python 中是可能的。

python-3.x 可变 可散列

评论

0赞 Alan 4/25/2021
您提供的链接确实在安德鲁·贾菲(Andrew Jaffe)的回答及其下的评论中回答了您的问题。您必须使用 __hash()__ 定义对象的哈希处理方式。您可以选择要包含的属性以及如何组合它们 - 这就是为什么 Python 默认使用对象 ID(不会更改),因为它无法猜测每个可能类的预期哈希值。

答:

3赞 Alan 4/25/2021 #1

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

python 对象的哈希值的默认值是对象 ID,该 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",])

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

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

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

如果在此方案中运行字典测试,会发生什么情况?

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

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

{<0x7f05bc1f1fd0>处的 main.test 对象: '第一个对象'}
{<main.test 对象在 0x7f05bc1f1fd0>: '对象用作键', <main.test 对象在 0x7f05bc1f1fd0>: '第二个对象'}
{<main.test 对象在 0x7f05bc1f1fd0>: '第三个对象', <main.test 对象在 0x7f05bc1f1fd0>: '第二个对象'}

第二次固定哈希算词测试的结果:

{<0x7fc7b5510fd0>处的 main.test 对象: '第一个对象'}
{<main.test 对象在 0x7fc7b5510fd0>: '第二个对象'}
{<main.test 对象在0x7fc7b5510fd0>处: '第三个对象'}