`如果键入dict`与`try / except` - 这是更具可读性的成语?

时间:2010-12-22 18:48:38

标签: python idioms readability defaultdict code-readability

我对成语和可读性有疑问,对于这个特例,似乎存在Python哲学的冲突:

我想从字典B中构建字典A.如果B中不存在特定键,则不执行任何操作并继续。

哪种方式更好?

try:
    A["blah"] = B["blah"]
except KeyError:
    pass

if "blah" in B:
    A["blah"] = B["blah"]

“做并请求原谅”与“简单明了”。

哪个更好,为什么?

12 个答案:

答案 0 :(得分:62)

例外不是条件限制。

条件版本更清晰。这很自然:这是简单的流量控制,这是条件的设计,而不是例外。

在循环中执行这些查找时,异常版本主要用作优化:对于某些算法,它允许从内部循环中消除测试。它没有这个好处。它有一个很小的优点,它可以避免两次说"blah",但如果你做了很多这样的事情,你应该有一个帮助move_key函数。

一般情况下,我强烈建议默认情况下坚持使用条件版本,除非您有特殊原因不这样做。条件语是显而易见的方式,这通常强烈建议您选择一种解决方案而不是另一种解决方案。

答案 1 :(得分:49)

还有第三种方法可以避免异常和双重查找,如果查找很昂贵,这可能很重要:

value = B.get("blah", None)
if value is None: 
    A["blah"] = value

如果您希望字典包含None值,您可以使用更为深奥的常量,例如NotImplementedEllipsis或创建一个新常量:

MyConst = object()
def update_key(A, B, key):
    value = B.get(key, MyConst)
    if value is not MyConst: 
        A[key] = value

无论如何,使用update()对我来说是最具可读性的选项:

a.update((k, b[k]) for k in ("foo", "bar", "blah") if k in b)

答案 2 :(得分:14)

根据我的理解,你想用dict B中的键值对更新dict A.

update是更好的选择。

A.update(B)

示例:

>>> A = {'a':1, 'b': 2, 'c':3}
>>> B = {'d': 2, 'b':5, 'c': 4}
>>> A.update(B)
>>> A
{'a': 1, 'c': 4, 'b': 5, 'd': 2}
>>> 

答案 3 :(得分:7)

Python性能维基直接引用:

  

除了第一次,每次看到一个单词时,if语句的测试失败。如果要计算大量单词,很多单词可能会出现多次。在一个值的初始化只发生一次并且该值的增加将多次发生的情况下,使用try语句会更便宜。

因此,似乎两种选择都是可行的,具体取决于情况。有关详细信息,您可以查看以下链接:Try-except-performance

答案 4 :(得分:3)

我认为这里的一般规则是A["blah"]通常会存在,如果是这样尝试 - 除非好,否则请使用if "blah" in b:

我认为“尝试”在时间上便宜但“除了”更贵。

答案 5 :(得分:3)

我认为第二个例子是你应该去的,除非这段代码有意义:

try:
    A["foo"] = B["foo"]
    A["bar"] = B["bar"]
    A["baz"] = B["baz"]
except KeyError:
    pass

请记住,只要密钥不在B中,代码就会中止。如果此代码有意义,那么您应该使用异常方法,否则使用测试方法。在我看来,因为它更短并且清楚地表达了意图,所以它比异常方法更容易阅读。

当然,告诉您使用update的人是正确的。如果您使用的是支持字典理解的Python版本,我非常喜欢这段代码:

updateset = {'foo', 'bar', 'baz'}
A.update({k: B[k] for k in updateset if k in B})

答案 6 :(得分:2)

其他语言中的规则是为特殊条件保留例外,即在常规使用中不会发生的错误。不知道该规则如何适用于Python,因为该规则不应存在StopIteration。

答案 7 :(得分:1)

就个人而言,我倾向于第二种方法(但使用has_key):

if B.has_key("blah"):
  A["blah"] = B["blah"]

这样,每个赋值操作只有两行(而不是4行,有try / except),任何抛出的异常都将是真正的错误或你错过的东西(而不仅仅是试图访问那些不是'那里)。

事实证明(请参阅您问题的评论),has_key已被弃用 - 所以我猜它最好写成

if "blah" in B:
  A["blah"] = B["blah"]

答案 8 :(得分:1)

除了讨论可读性之外,我认为性能在某些情况下也很重要。快速 timeit 基准测试表明测试(即“请求许可”)实际上比处理异常(即“请求宽恕”)略快。

这是设置基准的代码,生成一个较大的随机键值对字典:

setup = """
import random, string
d = {"".join(random.choices(string.ascii_letters, k=3)): "".join(random.choices(string.ascii_letters, k=3)) for _ in range(10000)}
"""

然后是 if 测试:

stmt1 = """
key = "".join(random.choices(string.ascii_letters, k=3))
if key in d:
    _ = d[key]
"""

给我们:

>>> timeit.timeit(stmt=stmt1, setup=setup, number=1000000)
1.6444563979999884

而利用异常的方法

stmt2 = """
key = "".join(random.choices(string.ascii_letters, k=3))
try:
    _ = d[key]
except KeyError:
    pass
"""

给我们:

>>> timeit.timeit(stmt=stmt2, setup=setup, number=1000000)
1.8868465850000575

有趣的是,将 key 代从实际基准测试提升到设置中,然后一遍又一遍地寻找相同键,提供了截然不同的数字:

>>> timeit.timeit(stmt=stmt1, setup=setup, number=100000000)
2.3290171539999847
>>> timeit.timeit(stmt=stmt2, setup=setup, number=100000000)
26.412447488999987

我不想推测这是否强调了测试与异常处理的好处,或者字典是否缓冲了先前查找的结果,从而使基准测试结果偏向于测试......?

答案 9 :(得分:0)

为什么不这样做:

def try_except(x,col):
    try:
        return x[col]
    except:
        return None

list(map(lambda x: try_except(x,'blah'),A))

答案 10 :(得分:0)

@Suppress("UNCHECKED_CAST") @Throws(Exception::class) fun addAdditionalEnvironmentVariables(additionalEnvironmentVariables: Map<String, String>) { try { val processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment") val theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment") theEnvironmentField.isAccessible = true val env = theEnvironmentField.get(null) as MutableMap<String, String> env.putAll(additionalEnvironmentVariables) val theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment") theCaseInsensitiveEnvironmentField.isAccessible = true val cienv = theCaseInsensitiveEnvironmentField.get(null) as MutableMap<String, String> cienv.putAll(additionalEnvironmentVariables) } catch (e: NoSuchFieldException) { val classes = Collections::class.java.getDeclaredClasses() val env = System.getenv() for (cl in classes) { if ("java.util.Collections\$UnmodifiableMap" == cl.getName()) { val field = cl.getDeclaredField("m") field.setAccessible(true) val obj = field.get(env) val map = obj as MutableMap<String, String> map.clear() map.putAll(additionalEnvironmentVariables) } } } } 开始并引入assignment expressions (PEP 572)Python 3.8运算符),我们可以在变量:=中捕获条件值dictB.get('hello', None),以便都检查它是否不是value(因为None返回关联的值或dict.get('hello', None)),然后在条件的正文中使用它:

None

答案 11 :(得分:0)

尽管公认的答案强调“跨越式先行”原则可能适用于大多数语言,但基于python原则,更多的pythonic可能是第一种方法。更不用说这是python中的合法编码风格。重要的是要确保在正确的上下文中使用tryexcept块并遵循最佳实践。例如。在try块中做太多的事情,捕获到非常广泛的异常,或更糟的是-仅有的except子句等。

  

请求宽恕比允许容易。 (EAFP)

请参阅python文档参考here

另外,来自核心开发人员之一的布雷特(Brett)的blog简要地介绍了其中的大部分内容。

查看另一个SO讨论here