Determine whether a key is present in a dictionary
Possible Duplicate:
'has_key()' or 'in'?
我有一本Python字典,像:
1
| mydict = {'name':'abc','city':'xyz','country','def'} |
我想检查一下字典里有没有一个键。我很想知道下面两个案例中哪一个更可取,为什么呢?
1 2
| 1> if mydict.has_key('name'):
2> if 'name' in mydict: |
- 顺便说一句,dict是内置python类型的名称,因此最好避免在脚本中使用它作为变量名(尽管严格来说,这样做是合法的)。
- 文件很清楚,不是吗?
- 在python 3中,dict对象不再有has_key()方法,因此版本可移植性方面,in操作符更好。
是首选的Python型。不鼓励使用has_key(),并且该方法已在python 3中被删除。
- 此外,"name" in dict还可以用于任何可检索的词典,而不仅仅是字典。
- 那dict.get(key)呢?这也应该避免?
- @纸浆虚构:当你(1)不想使用KeyError时,key不在dict(2)中,如果没有key想使用默认值(dict.get(key, default)时,dict.get(key)是有用的。点2也可以使用defaultdict完成。
- @马诺:啊,谢谢。
- get返回值。它不能(也不能)可靠地告诉您该键是否在字典中。这是一个完全不同的目的。
- @乔,1)它可以可靠地告诉你,但是仅仅用它来解决这个问题当然是愚蠢的,2)马诺吉正在更高层次上解决这个问题。您通常有理由检查某把钥匙是否在口述中,而且get、setdefault和defaultdict通常会更顺利地处理这些原因。
正如马蒂诺的反应一样,最好的解决办法往往是不检查。例如,代码
1 2 3 4
| if x in d:
foo = d[x]
else:
foo = bar |
通常是写的
它更短,更直接地表达了你的意思。
另一个常见的例子是
1 2 3 4
| if x not in d:
d[x] = []
d[x].append(foo) |
可以重写的
1
| d.setdefault(x, []).append(foo) |
或者用collections.defaultdict(list)代替d,再写得更好。
- 是的,你可以称之为"智能默认值"(甚至是"智能设计")—
- 不,这些是python随时间演化的方法和类型。;)
- 天哪,你说得对!
- @马提诺通常是一个设计过程,最初不能产生一个好的解决方案。直到真正的解决方案成功,它才能得到改进。一般来说,如果有足够的随机突变,其中一个将优于最初的设计。
- @既然我们不是在讨论自然,我希望突变不是完全随机的。正如弗雷德里克·布鲁克斯在1975年出版的《神秘人月》一书中所说,"计划扔掉一个,无论如何你会的"。真正的缺点是,imho,通常你不能真正负担得起,最终不得不向后兼容,因为在进化过程中产生了许多依赖关系。这就是为什么最好的设计往往是那些减少依赖性的设计。
在字节码方面,in保存了LOAD_ATTR并用COMPARE_OP代替了CALL_FUNCTION。
1 2 3 4 5 6 7 8 9 10 11 12 13
| >>> dis.dis(indict)
2 0 LOAD_GLOBAL 0 (name)
3 LOAD_GLOBAL 1 (d)
6 COMPARE_OP 6 (in)
9 POP_TOP
>>> dis.dis(haskey)
2 0 LOAD_GLOBAL 0 (d)
3 LOAD_ATTR 1 (haskey)
6 LOAD_GLOBAL 2 (name)
9 CALL_FUNCTION 1
12 POP_TOP |
我的感觉是,in更易读,在我能想到的任何情况下都是首选。
在性能方面,定时反映了操作码
1 2 3 4 5
| $ python -mtimeit -s'd = dict((i, i) for i in range(10000))'"'foo' in d"
10000000 loops, best of 3: 0.11 usec per loop
$ python -mtimeit -s'd = dict((i, i) for i in range(10000))'"d.has_key('foo')"
1000000 loops, best of 3: 0.205 usec per loop |
in的速度几乎是前者的两倍。
- 当然,任何速度度量都是特定于问题的,通常是不相关的,依赖于实现的,可能依赖于版本的,并且比否决和样式问题不那么重要。
- @迈克·格雷厄姆,你基本上是对的。我坚持认为更糟的情况是因为,在我看来,那是你真正想知道的地方。另外,我认为你的态度是(虽然仍然绝对正确的),稍微更适合C这样的语言,在这种语言中,任何一种方式都很快,除非你真的把事情搞砸了。在python中,更大程度地正确使用它是值得的。此外,核心开发人员也有一种调整"唯一正确的方法"的方法来做一些事情,这样,性能在很大程度上比语言中的正常情况更能显示良好的风格。
我的答案是"两者都不是"。
我认为做事情的最"Python式"的方法是不事先检查字典中是否有密钥,而是编写假定它存在的代码,并捕获由于它不存在而引发的任何密钥错误。
这通常是通过将代码包含在try...except子句中来完成的,这是一个众所周知的习惯用法,通常表示为"请求原谅比请求允许更容易",或者缩写为eafp,这基本上意味着在做任何事情之前最好尝试并捕获错误,而不是确保一切正常。当您可以优雅地处理异常而不是试图避免它们时,为什么要验证不需要验证的内容?因为它的可读性通常更高,如果密钥不在的概率很低(或者可能存在的任何先决条件),代码的速度就会更快。
当然,这并不适用于所有情况,也不是每个人都同意这一理念,因此您需要根据具体情况自行决定。不足为奇,与此相反的是"三思而后行"的lbyl。
作为一个简单的例子,请考虑:
1 2 3 4 5
| if 'name' in dct:
value = dct['name'] * 3
else:
logerror('"%s" not found in dictionary, using default' % name)
value = 42 |
VS
1 2 3 4 5
| try:
value = dct['name'] * 3
except KeyError:
logerror('"%s" not found in dictionary, using default' % name)
value = 42 |
尽管在这种情况下,代码量几乎完全相同,但第二个代码不会首先花费时间进行检查,因此可能稍微快一点(尝试一下……不过,除了块不是完全免费的,所以在这里可能不会有太大的区别)。
一般来说,提前测试通常会涉及到更多的内容,而不这样做可以节省大量的成本。也就是说,由于其他答案中所述的原因,if 'name' in dict:更好。
如果您对这个主题感兴趣,那么来自python邮件列表存档的标题为"eafp vs lbyl(was re:a little dissafess so far)"的消息很可能解释了这两种方法之间的区别,比我在这里做的更好。还有一个很好的讨论,是关于《简而言之,python》这本书中的两种方法,这本书是亚历克斯·马泰利在第6章的第2版,题目是"错误检查策略"。(我看到现在有一个更新的第三版,于2017年出版,涵盖了python 2.7和3.x)。
- 是否有数据支持"不这样做会带来巨大的节约收益"的说法?作为一名Java开发人员,我习惯于认为异常是昂贵的,应该是真正的例外情况。你的建议听起来像是"作为goto的例外"。你能举出来源吗?
- "达夫莫。不,我不能引用来源。我之所以这样说,是因为通常有很多方法会出错,而正确的方法相对较少。检查所有错误的方法可能涉及大量的代码(编写代码通常也很繁琐)。在某些语言中,处理异常可能很慢,我特别提到,如果您希望它们经常发生,那么这可能不是一个好的方法。我也不主张将异常作为程序正常或常规控制流的一部分——正如您所说,它们应该用于异常情况。
- 在python中,异常是昂贵的。如果您期望键丢失超过百分之几的时间,异常成本可能会支配函数的运行时。
- @蒂姆-谢谢。我很欣赏这个解释,因为我也在学习Python。
- @Duffymo,在Python中流行的样式是使用异常。这将创建更为惯用、可读的代码。一般来说,后续的try块相当便宜,但如果引发异常,则会更昂贵,但这并不是您编写的95%代码的设计所必需的。
- @蒂姆:你错过了我说的"如果你希望钥匙丢失的时间超过百分之几"的地方吗?异常只有在语句不发生的情况下才会像if语句一样快——如果确实发生了,那么对于零除法,您的链接显示它们慢了2倍,而对于dict查找,它显示它们慢了10倍。别看"Python",我要看速度快10倍的成语。
- @乔。如果您希望事情发生得相对频繁,那么提前检查它比使用异常更快,因为异常发生时处理速度较慢。您的代码可能会因为额外的检查而更加复杂,但这就是权衡。异常的发生不应该是"正常"的程序流,通常是针对那些不希望经常发生的事情(它们是异常的)。
- 我必须验证,因为JavaScript中的Try/Catch速度慢了100%。"如果没有引发异常,那么try/except块是非常有效的"-python.org