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

发布于 2024-10-08 23:48:50 字数 318 浏览 5 评论 0原文

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

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

哪种方式更好?

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

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

“做并请求宽恕”与“简单和明确”。

哪个更好,为什么?

I have a question about idioms and readability, and there seems to be a clash of Python philosophies for this particular case:

I want to build dictionary A from dictionary B. If a specific key does not exist in B, then do nothing and continue on.

Which way is better?

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

or

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

"Do and ask for forgiveness" vs. "simplicity and explicitness".

Which is better and why?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

东风软 2024-10-15 23:48:50

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

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)

There is also a third way that avoids both exceptions and double-lookup, which can be important if the lookup is expensive:

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

In case you expect the dictionary to contain None values, you can use some more esoteric constants like NotImplemented, Ellipsis or make a new one:

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

Anyway, using update() is the most readable option for me:

a.update((k, b[k]) for k in ("foo", "bar", "blah") if k in b)
∞觅青森が 2024-10-15 23:48:50

例外不是条件。

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

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

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

Exceptions are not conditionals.

The conditional version is clearer. That's natural: this is straightforward flow control, which is what conditionals are designed for, not exceptions.

The exception version is primarily used as an optimization when doing these lookups in a loop: for some algorithms it allows eliminating tests from inner loops. It doesn't have that benefit here. It has the small advantage that it avoids having to say "blah" twice, but if you're doing a lot of these you should probably have a helper move_key function anyway.

In general, I'd strongly recommend sticking with the conditional version by default unless you have a specific reason not to. Conditionals are the obvious way to do this, which is usually a strong recommendation to prefer one solution over another.

李不 2024-10-15 23:48:50

据我了解,您想用字典 B 中的键值对更新字典 A

更新 是一个更好的选择。

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

From what I understand, you want to update dict A with key,value pairs from dict B

update is a better choice.

A.update(B)

Example:

>>> 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}
>>> 
仅此而已 2024-10-15 23:48:50

直接引用Python性能wiki:

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

因此,根据具体情况,这两种选择似乎都是可行的。有关更多详细信息,您可能想查看此链接: 尝试-除了性能

Direct quote from Python performance wiki:

Except for the first time, each time a word is seen the if statement's test fails. If you are counting a large number of words, many will probably occur multiple times. In a situation where the initialization of a value is only going to occur once and the augmentation of that value will occur many times it is cheaper to use a try statement.

So it seems that both options are viable depending from situation. For more details you might like to check this link out: Try-except-performance

纸伞微斜 2024-10-15 23:48:50

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

I认为“尝试”在时间上是便宜的,但“除外”则更昂贵。

I think the general rule here is will A["blah"] normally exist, if so try-except is good if not then use if "blah" in b:

I think "try" is cheap in time but "except" is more expensive.

酒中人 2024-10-15 23:48:50

我认为除非此代码有意义,否则您应该采用第二个示例:

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

I think the second example is what you should go for unless this code makes sense:

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

Keep in mind that code will abort as soon as there is a key that isn't in B. If this code makes sense, then you should use the exception method, otherwise use the test method. In my opinion, because it's shorter and clearly expresses the intent, it's a lot easier to read than the exception method.

Of course, the people telling you to use update are correct. If you are using a version of Python that supports dictionary comprehensions, I would strongly prefer this code:

updateset = {'foo', 'bar', 'baz'}
A.update({k: B[k] for k in updateset if k in B})
葬心 2024-10-15 23:48:50

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

The rule in other languages is to reserve exceptions for exceptional conditions, i.e. errors that don't occur in regular use. Don't know how that rule applies to Python, as StopIteration shouldn't exist by that rule.

十秒萌定你 2024-10-15 23:48:50

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

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

Starting Python 3.8, and the introduction of assignment expressions (PEP 572) (:= operator), we can capture the condition value dictB.get('hello', None) in a variable value in order to both check if it's not None (as dict.get('hello', None) returns either the associated value or None) and then use it within the body of the condition:

# dictB = {'hello': 5, 'world': 42}
# dictA = {}
if value := dictB.get('hello', None):
  dictA["hello"] = value
# dictA is now {'hello': 5}
寻梦旅人 2024-10-15 23:48:50

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

有趣的是,提升 >密钥从实际基准生成到设置中,然后一遍又一遍地寻找相同密钥,提供了截然不同的数字:

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

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

In addition to discussing readability, I think performance also matters in some scenarios. A quick timeit benchmark indicates that a test (i.e. “asking permission”) is actually slightly faster than handling the exception (i.e. “asking forgiveness”).

Here’s the code to set up the benchmark, generating a largeish dictionary of random key-value pairs:

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

Then the if test:

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

gives us:

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

whereas the approach utilizing the exception

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

gives us:

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

Interestingly, hoisting the key generation from the actual benchmark into the setup and therewith looking for the same key over and over, delivers vastly different numbers:

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

I don’t want to speculate whether this emphasizes the benefits of a test vs. exception handling, or if the dictionary buffers the result of the previous lookup and thus biases the benchmark results towards testing… ????

噩梦成真你也成魔 2024-10-15 23:48:50

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

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

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

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

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

Personally, I lean towards the second method (but using has_key):

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

That way, each assignment operation is only two lines (instead of 4 with try/except), and any exceptions that get thrown will be real errors or things you've missed (instead of just trying to access keys that aren't there).

As it turns out (see the comments on your question), has_key is deprecated - so I guess it's better written as

if "blah" in B:
  A["blah"] = B["blah"]
转瞬即逝 2024-10-15 23:48:50

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

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

请参阅此处的 python 文档参考。

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

请参阅此处<的另一个SO讨论< /a>:

Though the accepted answer's emphasize on "look before you leap" principle might apply to most languages, more pythonic might be the first approach, based on the python principles. Not to mention it is a legitimate coding style in python. Important thing is to make sure you are using the try except block in the right context and is following best practices. Eg. doing too many things in a try block, catching a very broad exception, or worse- the bare except clause etc.

Easier to ask for forgiveness than permission. (EAFP)

See the python docs reference here.

Also, this blog from Brett, one of the core devs, touches most of this in brief.

See another SO discussion here:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文