【问题标题】:Determine whether a key is present in a dictionary [duplicate]确定字典中是否存在键[重复]
【发布时间】:2010-09-17 09:09:03
【问题描述】:

可能重复:
'has_key()' or 'in'?

我有一个 Python 字典,例如:

mydict = {'name':'abc','city':'xyz','country','def'}

我想检查一个键是否在字典中。 我很想知道以下两种情况哪个更可取,为什么?

1> if mydict.has_key('name'):
2> if 'name' in mydict:

【问题讨论】:

  • 顺便说一句,dict 是 Python 内置类型的名称,因此最好避免在脚本中将其用作变量名(尽管严格来说,这样做是合法的)。
  • docs are quite clear,不是吗?
  • 在 Python 3 中,dict 对象不再具有 has_key() 方法,因此在版本可移植性方面,in 运算符更好。

标签: python dictionary


【解决方案1】:
if 'name' in mydict:

是首选的 Pythonic 版本。不鼓励使用has_key(),此方法has been removed in Python 3

【讨论】:

  • 另外,"name" in dict 将适用于任何可迭代的,而不仅仅是字典。
  • dict.get(key) 怎么样?这也应该避免?
  • @PulpFiction: dict.get(key) 在您 (1) 不想要 KeyError 以防 key 不在 dict (2) 想要使用默认值时很有用如果没有key (dict.get(key, default))。第 2 点也可以使用 defaultdict 完成。
  • dict.get 返回值。它不会(也不能)可靠地告诉您密钥是否在字典中。这是一个完全不同的目的。
  • @Joe,1) 它可以可靠地告诉您这一点,但是仅仅使用它当然是愚蠢的,并且 2) Manoj 正在更高级别解决这个问题.您通常有理由检查某个键是否在 dict 中,而 getsetdefaultdefaultdict 经常会更顺利地处理这些原因。
【解决方案2】:

与 martineau 的回答一样,最好的解决方案通常是不检查。比如代码

if x in d:
    foo = d[x]
else:
    foo = bar

正常书写

foo = d.get(x, bar)

更短更直接地表达了你的意思。

另一个常见的情况是这样的

if x not in d:
    d[x] = []

d[x].append(foo)

可以改写

d.setdefault(x, []).append(foo)

或者通过将collections.defaultdict(list) 用于d 和写作来更好地重写

d[x].append(foo)

【讨论】:

  • 是的,你可以称之为“智能默认值”(甚至是“智能设计”;-)
  • 不,这些是 Python 随时间演变的方法和类型。 ;)
  • @martineau 一般来说,设计过程最初无法产生好的解决方案。直到解决方案成为现实,才能对其进行改进。一般来说,如果有足够多的随机突变,其中一个会优于原始设计。
  • @AaronMcSmooth 因为我们在这里不是在谈论自然,所以我希望突变不是完全随机的。正如弗雷德里克·布鲁克斯 (Frederick Brooks) 在其 1975 年的著作The Mythical Man-Month 中所说的那样,“计划扔掉一个;无论如何,你会的”。恕我直言,真正的缺点是,您通常负担不起这样做,并且由于在进化过程中出现的众多依赖关系而最终不得不向后兼容。这就是为什么最好的设计往往是那些减少依赖关系的设计。
【解决方案3】:

就字节码而言,in 保存了一个LOAD_ATTR 并将CALL_FUNCTION 替换为COMPARE_OP

>>> 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可读性更强,并且在我能想到的所有情况下都是首选。

在性能方面,时序反映了操作码

$ 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 几乎快两倍。

【讨论】:

  • 任何速度测量当然都是针对特定问题的,通常是不相关的、依赖于实现的、可能依赖于版本的,并且不如弃用和样式问题重要。
  • @Mike Graham,你是对的。我确实认为更糟糕的情况是因为,IMO,那是你真正想知道的地方。另外,我认为你的态度是(虽然仍然绝对正确),稍微更适合像 C 这样的语言,除非你真的把事情搞砸了,否则无论哪种方式都很快。在 Python 中,在更大程度上让它正确是值得的。此外,核心开发人员有一种方法可以调整“一种正确的方式”来做某事,这样,性能再次成为一种良好风格的良好指标,比语言中的正常情况更大。
【解决方案4】:

我的回答是“都不是”。

我相信最“Pythonic”的做事方式是不事先检查密钥是否在字典中,而是只编写假设它存在的代码并捕获任何因它不存在而引发的 KeyErrors。

这通常是通过将代码包含在try...except 子句中来完成的,这是一个众所周知的成语,通常表示为“请求宽恕比许可更容易”或首字母缩略词 EAFP,这基本上意味着最好尝试一些事情并捕获错误,而不是在做任何事情之前确保一切正常。当您可以优雅地处理异常而不是试图避免它们时,为什么要验证不需要验证的内容?因为它通常更具可读性,并且如果密钥不存在的可能性较低(或可能存在的任何先决条件),代码往往会更快。

当然,这并不适用于所有情况,也不是每个人都同意这一理念,因此您需要根据具体情况自行决定。毫不奇怪,与此相反的是 LBYL,意为“Look Before You Leap”。

作为一个简单的例子考虑:

if 'name' in dct:
    value = dct['name'] * 3
else:
    logerror('"%s" not found in dictionary, using default' % name)
    value = 42

try:
    value = dct['name'] * 3
except KeyError:
    logerror('"%s" not found in dictionary, using default' % name)
    value = 42

虽然在这种情况下它的代码量几乎完全相同,但第二个不会先花时间检查,因此可能会稍微快一些(try...except 块不是完全免费的,所以它可能在这里没有太大区别)。

一般而言,提前测试通常会涉及更多内容,并且不进行测试可以节省大量资金。也就是说,由于其他答案中所述的原因,if 'name' in dict: 更好。

如果您对该主题感兴趣,来自 Python 邮件列表存档的标题为“EAFP vs LBYL(回复:到目前为止有点失望)”的 message 可能解释了两者之间的区别两个人比我在这里更接近。在 Alex Martelli 的书 Python in a Nutshell, 2nd Ed 中关于异常的第 6 章标题为 Error-Checking Strategies 中也对这两种方法进行了很好的讨论。 (我看到现在有一​​个更新的 3rd edition,于 2017 年发布,涵盖 Python 2.7 和 3.x)。

【讨论】:

  • 是否有数据支持“不这样做可以节省大量资金”的说法?作为一名 Java 开发人员,我习惯于认为异常是昂贵的,应该用于真正的异常情况。您的建议听起来像“goto 例外”。你能引用一个来源吗?
  • Python 中的异常是昂贵的。如果您预计密钥丢失的时间超过百分之几,则异常成本可能会主导函数的运行时间。
  • @duffymo,Python 中的主流风格是使用异常。这会创建更惯用、更易读的代码。一般来说,一个成功的 try 块非常便宜,但如果引发异常,它的成本会更高,但这并不是决定你编写的 95% 代码的设计的原因
  • @Tim:您是否错过了我所说的“如果您预计密钥丢失的几率超过百分之几”?如果异常没有发生,它们的速度仅与 if 语句一样快 - 如果它们确实发生,则您的链接显示它们对于零除法要慢 2 倍,而我的快速 timeit 显示它们对于 dict 查找要慢 10x 。拧“Pythonic”,我会采用运行速度快 10 倍的习语。
  • @乔。如果您预计某些事情会相对频繁地发生,那么提前检查它比使用发生时处理速度较慢的异常要快。您的代码可能会因额外检查而变得更加复杂,但这就是权衡。发生的异常不应该是“正常”的程序流程,并且通常是针对那些预计不会经常发生的事情(它们是异常的)。
猜你喜欢
  • 2012-06-15
  • 2013-06-02
  • 2018-10-19
  • 2012-07-31
  • 1970-01-01
  • 2019-03-01
  • 1970-01-01
  • 2013-10-15
  • 2019-04-20
相关资源
最近更新 更多