Python-'if foo in dict'vs'try:dict [foo]'


24

我想这不是关于鸭子类型的本质的问题,而是关于保持pythonic的问题。

首先-当处理字典时,尤其是当字典的结构是相当可预测的并且给定的密钥通常不存在但有时存在时,我首先想到两种方法:

if myKey in dict:
    do_some_work(dict[myKey])
else:
    pass

当然,Ye Olde的“宽恕与许可”方法。

try:
    do_some_work(dict[myKey])
except KeyError:
    pass

作为一名Python的熟练手,我觉得后者是很多人的首选,我觉得这很奇怪,因为在Python文档中try/excepts,当出现实际错误时,似乎是首选,而不是…… 没有成功?

如果偶然的dict在myDict中没有密钥,并且已知它并不总是具有该密钥,那么try / except在上下文上是否会引起误解?这不是编程错误,只是数据的事实-该字典没有特定的键。

当您查看try / except / else语法时,这似乎特别重要,当确保try不会捕获太多错误时,这似乎非常有用。您可以执行以下操作:

try:
    foo += bar
except TypeError:
    pass
else:
    return some_more_work(foo)

这是否会导致吞没可能是某些错误代码导致的各种奇怪错误?上面的代码可能只是在阻止您看到您要添加的内容,2 + {}并且您可能永远都不会意识到代码的某些部分出现了严重错误。我不建议我们检查所有类型,这就是为什么它是Python而不是JavaScript的原因-但是在try / except的上下文中,似乎应该捕获程序正在执行的本来应该做的事情,而不是使它能够继续下去。

我意识到上面的例子是一个草率的论点,实际上是故意的。但是,鉴于better to ask forgiveness than permission我的Python信条,我不禁要问一个问题,那就是在if / else与try / except正确应用之间,实际上是在沙子上的线在哪里,特别是当您知道期望的结果时您正在使用的数据。

我什至没有在谈论速度问题或最佳实践,我只是对感知的维恩图感到困惑,在这种情况下,维恩图看起来可能会发生任何一种变化,但是人们会在尝试/例外方面犯错因为“某处有人说这是Pythonic”。我对这种语法的应用是否得出了错误的结论?

python 

我同意尝试除外的趋势存在隐藏错误的风险。我的看法是,除了希望看到的条件外,通常应该避免尝试(当然,某些例外适用于此规则)。
高登·比恩

Answers:


26

改用get方法

some_dict.get(the_key, default_value)

... default_value如果the_key不在中,返回的值在哪里some_dict?如果省略default_valueNone则如果缺少密钥,则返回。

通常,在Python中,人们倾向于选择try / except而不是先进行检查-请参阅词汇表中EAFP条目。请注意,许多“成员资格测试”功能在后台使用异常。


这样不是同样的问题吗?如果我们尝试使用a dict并根据发现的内容进行工作,则感觉像是if myDict.get(a_key) is not None: do_work(myDict[a_key])单词多了,又是跳跃之前隐式外观的另一个示例-加上恕我直言的可读性稍差。也许我没有dict.get(a_key, a_default)在正确的上下文中理解,但是它似乎是编写“非开关语句if / elses”或魔术值的先驱。我确实喜欢此方法的功能,我将对其进行更深入的研究。

1
@Stick-您这样做:data = myDict.get(a_key, default),或者do_work在给出default(例如None)时做正确的事情,或者您这样做if data: do_work(data)。两种情况下都只需要一次查找。(if data is not None: ...在任何一种情况下,可能都只是一次查找,然后您自己的逻辑就可以接管。)
2014年

1
因此,我认为dict.get()在当前项目中为我省了很多精力。它减少了我的行数,清除了许多看起来很糟糕的try/except/else垃圾,并且总体上提高了恕我直言我代码的可读性。我学到了一个非常有价值的课程,这是我之前听过的,但是却忽略了一个重要的步骤:“如果您觉得自己编写的代码太多,那就停下来看看语言功能。” 谢谢!!

@Stick-很高兴听到它:)我喜欢这个建议,我以前从未听过。
2014年

1
从技术上讲,哪个是最快的?
Olivier Pons

4

缺少密钥是规则还是例外

从实用的角度来看,投掷/捕获要慢于检查键是否在表中。如果丢失键是常见的情况-您应该使用condition。

支持条件的另一件事是else子句-当执行任何一个块时,要理解它要容易得多,请考虑甚至可以从try块中的多个语句中抛出相同的异常类。

except子句中的代码不应属于正常缺陷的一部分(例如,如果密钥不存在,则添加它)-它应该处理错误。


4
“从务实的角度来看,抛出/捕获比检查键是否在表中要慢得多”-不,不是。无论如何,不​​一定。最后我检查了一下,最常见的Python实现会更快地执行try/ catch版本。
2014年

2
在python中,throw / catch并不是一个重要的性能问题,在某种程度上,它通常用于python中的流控制。另外,在某些情况下,有可能在测试和访问之间修改该字典。
whatsisname 2014年

@whatsisname-我想将其添加到我的答案中,但是如果您有类似这样的种族危险,那么TBH会让您面临更大的问题,即EAFP和LBYL:P
不错,2014年

1

本着“ pythonic”的精神,尤其是鸭类打字的精神-try / except对我来说几乎是脆弱的。

您真正想要的只是具有类似dict的访问权限的东西,但是__getitem__可以被覆盖以做一些您所期望的事情。例如,使用defaultdict标准库中的:

In [1]: from collections import defaultdict

In [2]: foo = defaultdict(int)

In [3]: foo['a'] = 1

In [4]: foo['b']  # Someone did access checking with a try/except exactly as you have in the question
Out[4]: 0

In [5]: 'a' in foo
Out[5]: True

In [6]: 'b' in foo  # We never set it, but now it exists!
Out[6]: True

In [7]: 'c' in foo
Out[7]: False

因为默认返回值为Falsy,所以这样的访问检查在大多数情况下都可以正常工作。它似乎也使代码更简单,这就是为什么我和我的同事花了几个月的时间。

不幸的是,几个月后,这些额外的键实际上0变成了问题,并导致难以追踪的错误,因为它们变成了有效值。有时,我们使用in来检查,如果存在的关键,使代码做不同的事情,当它应该是相同的。

我认为它看起来很破损的原因是,按照Python的鸭子式精神,您只需要像dict一样的东西,并defaultdict完全符合该要求,就像您创建的任何继承自的对象一样dict。您不能保证调用方以后不会更改其实现,因此您应该以最小的副作用进行操作。

使用第一个版本if myKey in mydict


此外,请注意,这是专门关于这个问题的例子。python的信条是:“比请求更好的请求宽恕”主要是为了确保您在没有得到想要的东西时能够正确地采取行动。例如,检查文件是否存在,然后尝试从中读取文件-我想到的至少三件事可能会出错:

  • 竞争条件是文件可能已被删除/移动/等
  • 您没有对该文件的读取权限
  • 该文件已损坏

您可以尝试第一个“请求许可”,但是根据种族条件,它不一定总是有效。第二个太容易忘记检查了;第三,如果不尝试读取文件,则无法检查。在任何情况下,你失败几乎可以肯定将是相同的,所以在以后做这个例子,它更好地“请求原谅”通过使用try /除外。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.