繁体   English   中英

`if key in dict` 与 `try/except` - 哪个更易读?

[英]`if key in dict` vs. `try/except` - which is more readable idiom?

我有一个关于习语和可读性的问题,在这个特殊情况下似乎存在 Python 哲学的冲突:

我想从字典 B 构建字典 A。如果 B 中不存在特定键,则什么都不做并继续。

哪种方式更好?

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

要么

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

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

哪个更好?为什么?

例外不是条件。

有条件的版本更清楚。 这很自然:这是简单的流程控制,这是条件语句的设计目的,而不是例外。

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

一般来说,我强烈建议默认情况下坚持使用条件版本,除非您有特定的理由不这样做。 条件语句是执行此操作的明显方法,通常强烈建议优先选择一种解决方案而不是另一种解决方案。

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

value = B.get("blah", None)
if value is not 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)

据我了解,您想用字典 B 中的键值对更新字典 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}
>>> 

直接引用自 Python 性能 wiki:

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

因此,根据情况,这两种选择似乎都是可行的。 有关更多详细信息,您可能想查看此链接: Try-except-performance

我认为这里的一般规则是A["blah"]通常会存在if "blah" in b:

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

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

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})

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

Python 3.8开始,以及赋值表达式 (PEP 572):=运算符)的引入,我们可以在变量value中捕获条件值dictB.get('hello', None)以便同时检查它是否不是None ( as dict.get('hello', None)返回关联值或None ) 然后在条件体内使用它:

# dictB = {'hello': 5, 'world': 42}
# dictA = {}
if value := dictB.get('hello', None):
  dictA["hello"] = value
# dictA is now {'hello': 5}

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

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

这样,每个赋值操作只有两行(而不是使用 try/except 的 4 行),抛出的任何异常都将是真正的错误或您错过的事情(而不是仅仅尝试访问不存在的键) .

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

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

尽管接受的答案强调“三思而后行”原则可能适用于大多数语言,但基于 python 原则,更多 pythonic 可能是第一种方法。 更不用说它是 python 中合法的编码风格。 重要的是确保您在正确的上下文中使用 try except 块并遵循最佳实践。 例如。 在一个 try 块中做太多事情,捕获一个非常广泛的异常,或者更糟的是 - 简单的 except 子句等。

请求原谅比获得许可更容易。 (EAFP)

请在此处查看 python 文档参考。

此外,来自核心开发人员之一 Brett 的这篇博客简要介绍了其中的大部分内容。

在此处查看另一个 SO 讨论:

除了讨论可读性,我认为性能在某些场景下也很重要。 快速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

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

为什么不这样做:

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM