作者:Inotime 
来源:CSDN 
原文:https://blog.csdn.net/lnotime/article/details/81192207

答:一个对象能不能作为字典的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中字典的key都可以是什么的更多相关文章

  1. python中字典以key排序,以value排序。以及通过value找key的方式

    1.sorted函数首先介绍sorted函数,sorted(iterable,key,reverse),sorted一共有iterable,key,reverse这三个参数. 其中iterable表示 ...

  2. Python中字典和集合

    Python中字典和集合 映射类型: 表示一个任意对象的集合,且可以通过另一个几乎是任意键值的集合进行索引 与序列不同,映射是无序的,通过键进行索引 任何不可变对象都可用作字典的键,如字符串.数字.元 ...

  3. python接口自动化(九)--python中字典和json的区别(详解)

    简介 这篇文章的由来是由于上一篇发送post请求的接口时候,参数传字典(dict)和json的缘故,因为python中,json和dict非常类似,都是key-value的形式,为啥还要这么传参,在群 ...

  4. python中字典和json的区别

    python中,json和dict非常类似,都是key-value的形式,而且json.dict也可以非常方便的通过dumps.loads互转 定义 python中,json和dict非常类似,都是k ...

  5. python中字典的循环遍历的两种方式

    开发中经常会用到对于字典.列表等数据的循环遍历,但是python中对于字典的遍历对于很多初学者来讲非常陌生,今天就来讲一下python中字典的循环遍历的两种方式. 注意: python2和python ...

  6. python中字典排序,列表中的字典排序

    python中字典排序,列表中的字典排序 一.使用python模块:operator import operator #首先要导入模块operator x = {1:2, 3:4, 4:3, 2:1, ...

  7. Python中sorted(iterable, /, *, key=None, reverse=False)的参数中的斜杆是什么意思?

    通过help(sorted)查看sorted的帮助文档,显示如下: Help on built-in function sorted in module builtins: sorted(iterab ...

  8. 字典的key都可以是什么

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

  9. Python中字典的详细用法

    #字典 #字典是Python中唯一内建的映射类型.字典中没有特殊的顺序,但都是存储在一个特定的键(key)下面,键可以是数字,字符串,甚至是元组 #一.字典的使用 #在某些情况下,字典比列表更加适用: ...

随机推荐

  1. python入门之数据类型之列表、元组、字典

    list 格式: test_list = ["a",123,[1,"b"]] 索引: >>>print(test_list[0]) " ...

  2. [异常]undefined method `visit' for #<RSpec::Core::ExampleGroup::Nested_1:0x16529f8 @example=nil>

    在进行Rspec 编译测试: bundle exec rspec spec/requests/static_pages_spec.rb 提示错误: FF Failures: 1) Static pag ...

  3. go实现生产者消费者

    package main import ( "fmt" "math/rand" ) func main() { ch := make(chan int) don ...

  4. 使用Spring Cloud Feign

    使用Spring Cloud Feign作为HTTP客户端调用远程HTTP服务 在spring Cloud Netflix栈中,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就 ...

  5. Windows 10下mysql 64位 安装(mysql-5.7.11-winx64安装)

    Windows下mysql 64位 安装(mysql-5.7.11-winx64安装) 系统Windows10 安装包mysql-5.7.11-winx64.zip 安装过程中遇到的问题,请留意4.0 ...

  6. arcgis jsapi接口入门系列(6):样式

    symbol: function () { //线样式 //样式详情请看官方文档 let style = { //线颜色,支持多种格式: //CSS color string:例如"dodg ...

  7. Web开发者应掌握的12个Firebug技巧

    来源: 廖煜嵘 相信很多从事Web开发工作的开发者都听说和使用过Firebug,但可能大部分人还不知道,其实它是一个在网页设计方面功能相当强大的编辑器,它 可以对HTML.DOM.CSS.HTTP和J ...

  8. uvm_factory——我们的工厂(一)

    factory 机制是实现(功能):通过一个字符串来创建此字符串所代表的的类的一个实例. //----------------------------------------------------- ...

  9. Eclipse介绍

    Eclipse是著名的跨平台开源集成开发环境(IDE).最初主要用来Java语言开发.Eclipse的本身只是一个框架平台,通过插件使其作为C/C++.Python.PHP等其他语言的开发工具.Ecl ...

  10. FPGA的嵌入式RAM

    FPGA中的嵌入式RAM分为两种:专用的BRAM和分布是RAM(用LUT实现的).这两种RAM又可以配置成单端口和双端口的RAM和ROM.双端口RAM又可以根据读写地址是否在同一块分为Double P ...