解读Python中字典的key都可以是什么

 更新时间:2022年09月27日 16:40:52   作者:Inotime  
这篇文章主要介绍了解读Python中字典的key都可以是什么,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

Python字典的key都可以是什么

一个对象能不能作为字典的key,就取决于其有没有__hash__方法。所以所有python自带类型中,除了list、dict、set和内部至少带有上述三种类型之一的tuple之外,其余的对象都能当key。

比如数值/字符串/完全不可变的元祖/函数(内建或自定义)/类(内建或自定义)/方法/包等等你能拿出手的,不过有的实际意义不高。还有数值型要注意,因为两个不同的相等数字可以有相同的哈希值,比如1和1.0。

解释

代码版本:3.6.3;文档版本:3.6.6

Unlike sequences, which are indexed by a range of numbers, dictionaries are indexed by keys, which can be any immutable type; strings and numbers can always be keys. Tuples can be used as keys if they contain only strings, numbers, or tuples; if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key. You can’t use lists as keys, since lists can be modified in place using index assignments, slice assignments, or methods like append()and extend().

 字典的键可以是任意不可变类型,需要注意的是tuple元组作为键时,其中不能以任何方式包含可变对象。

 那。。到底什么样的是不可变类型呢?不可能给对象专门标注一个属性是可变类型还是不可变类型啊,这没有任何其他意义,一定是通过其他途径实现的。把list当做键试一下

a = [1, 2, 3]
d = {a: a}
 
 
# 第二行报错:
# TypeError: unhashable type: 'list'

报错说list类型是不可哈希的,噢,原来是靠能不能hash来判断的,另外文档下面接着说同一字典中每个键都是唯一的,正好每个对象的哈希值也是唯一的,对应的很好。

It is best to think of a dictionary as an unordered set of key: value pairs, with the requirement that the keys are unique (within one dictionary).

查看源代码可以看到object对象是定义了__hash__方法的,

而list、set和dict都把__hash__赋值为None了

# 部分源码
 
class object:
    """ The most base type """
 
    def __hash__(self, *args, **kwargs):  # real signature unknown
        """ Return hash(self). """
        pass
 
 
class list(object):
    __hash__ = None
 
 
class set(object):
    __hash__ = None
 
 
class dict(object):
    __hash__ = None

那这样的话。。。我给他加一个hash不就能当字典的key了,key不就是可变的了。

注意

此处只是我跟着想法随便试,真的应用场景不要用可变类型作为字典的key。

class MyList(list):
    """比普通的list多一个__hash__方法"""
 
    def __hash__(self):
        # 不能返回hash(self)
        # hash(self)会调用self的本方法,再调用回去,那就没完了(RecursionError)
        # 用的时候要注意实例中至少有一个元素,不然0怎么取(IndexError)
        return hash(self[0])
 
 
l1 = MyList([1, 2])  # print(l1) -> [1, 2]
d = {l1: 'Can?'}
print(d)  # -->  {[1, 2]: 'Can?'}
l1.append(3)
print(d)  # {[1, 2, 3]: 'Can?'}
print(d[l1])  # -->  Can?

到这里就可以肯定的说,一个对象能不能作为字典的key,就取决于其有没有__hash__方法。所以所有python自带类型中,目前我已知的除了list、dict、set和内部带有以上三种类型的tuple之外,其余的对象都能当key。而我们自己定义的类,一般情况下都直接间接的和object有关,都带有__hash__方法。

另外我想到,既然字典的键是唯一的,而哈希值也是唯一的,这么巧,键的唯一性不会就是用哈希值来确定的吧?我上一个例子中__hash__方法返回的是0号元素的哈希值,那我直接用相同哈希值的对象是不是就能改变那本来不属于它的字典值呢?

class MyList(list):
    def __hash__(self):
        return hash(self[0])
 
 
l1 = MyList([1, 2])  # print(l1) -> [1, 2]
d = {}
d[l1] = l1
print(d)  # {[1, 2]: [1, 2]}
d[1] = 1
print(d)  # {[1, 2]: [1, 2], 1: 1}

竟然没有改成功而是新添加了一个键值对,可self[0]就是1啊,哈希值一样啊,怎么会不一样呢?难道要键的值一样才能判断是同一个键吗?重写__eq__方法试一下。

class MyList(list):
    def __hash__(self):
        return hash(self[0])
 
    def __eq__(self, other):
        return self[0] == other
 
 
l1 = MyList([1, 2])  # print(l1) -> [1, 2]
d = {}
d[l1] = l1
print(d)  # {[1, 2]: [1, 2]}
d[1] = 1
print(d)  # {[1, 2]: 1}

这回成功了,那就是__hash__返回值相等,且eq判断也相等,才会被认为是同一个键。那这两个先判断哪个呢?加个代码试一下

class MyList(list):
    def __hash__(self):
        print('hash is run')
        return hash(self[0])
 
    def __eq__(self, other):
        print('eq is run')
        return self[0] == other
 
 
l1 = MyList([1, 2])  # print(l1) -> [1, 2]
d = {}
d[1] = 1
d[l1] = 'l1'
print(d)
 
 
# 结果:
# hash is run
# eq is run
# {1: 'l1'}

__hash__先执行,另外字典在内存中存储数据的位置和键的hash也是有关的,逻辑上也像印证。

先计算hash,找到相对应的那片内存空间,里面没有值的话就直接写入,对于字典来说就是新增键值对;如果里面已经有值了,那就判断新来的键和原来的那里的键是不是相等,相等就认为是一个键,对于字典来说就是更新值,不相等就再开空间,相当于字典新增键值对。

在你验证自己想法的时候可能遇到__hash__和__eq__的一些想不到的麻烦,可以看这里:__hash__和__eq__的继承使用问题

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • python利用cv2库读取和保存视频的操作步骤

    python利用cv2库读取和保存视频的操作步骤

    这篇文章主要介绍了python利用cv2库读取和保存视频的操作步骤,文中通过代码示例给大家讲解的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-04-04
  • 通过实例解析Python return运行原理

    通过实例解析Python return运行原理

    这篇文章主要介绍了通过实例解析Python return运行原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • django正续或者倒序查库实例

    django正续或者倒序查库实例

    这篇文章主要介绍了django正续或者倒序查库实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-05-05
  • 解决pytorch GPU 计算过程中出现内存耗尽的问题

    解决pytorch GPU 计算过程中出现内存耗尽的问题

    今天小编就为大家分享一篇解决pytorch GPU 计算过程中出现内存耗尽的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-08-08
  • 详解Python中 sys.argv[]的用法简明解释

    详解Python中 sys.argv[]的用法简明解释

    本篇文章主要介绍了详解Python中 sys.argv[]的用法简明解释,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • python多进程中的生产者和消费者模型详解

    python多进程中的生产者和消费者模型详解

    这篇文章主要介绍了python多进程中的生产者和消费者模型,生产者是指生产数据的任务,消费者是指消费数据的任务。当生产者的生产能力远大于消费者的消费能力,生产者就需要等消费者消费完才能继续生产新的数据
    2023-03-03
  • pandas将DataFrame的列变成行索引的方法

    pandas将DataFrame的列变成行索引的方法

    下面小编就为大家分享一篇pandas将DataFrame的列变成行索引的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-04-04
  • keras slice layer 层实现方式

    keras slice layer 层实现方式

    这篇文章主要介绍了keras slice layer 层实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-06-06
  • 在python中实现将一张图片剪切成四份的方法

    在python中实现将一张图片剪切成四份的方法

    今天小编就为大家分享一篇在python中实现将一张图片剪切成四份的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-12-12
  • python3解析库BeautifulSoup4的安装配置与基本用法

    python3解析库BeautifulSoup4的安装配置与基本用法

    简单来说,BeautifulSoup就是Python的一个HTML或XML的解析库,我们可以用它来方便地从网页中提取数据,下面这篇文章主要给大家介绍了关于python3解析库BeautifulSoup4的安装配置与基本用法的相关资料,需要的朋友可以参考下
    2018-06-06

最新评论