哪种风格更受欢迎?

9

选项1:

def f1(c):
  d = {
    "USA": "N.Y.",
    "China": "Shanghai"
  }

  if c in d:
    return d[c]

  return "N/A"

选项2:
def f2(c):
  d = {
    "USA": "N.Y.",
    "China": "Shanghai"
  }

  try:
    return d[c]
  except:
    return "N/A"

这样我就可以调用:

for c in ("China", "Japan"):
  for f in (f1, f2):
    print "%s => %s" % (c, f(c))

选项是事先确定键是否在目录中(f1),或者只是回退到异常(f2)。哪个更好?为什么?

8
除外情况: 是不好的业力。在捕捉内容时要明确具体,例如在这种情况下捕捉 KeyError。 - richo
2
你选择了一个不好的例子。显而易见的答案与风格无关。 - Omnifarious
7个回答

22

我不会选择其中任何一个,而是会去选择

def f2(c):
  d = {
    "USA": "N.Y.",
    "China": "Shanghai"
  }

  return d.get(c, "N/A")
这种方式更简短,而且“get”是专门设计用于此任务的。此外,没有明确异常的except是不好的实践,因此请使用except KeyError:而不仅仅是except。在Python中,异常没有太多的开销。通常最好在没有更好的替代方法时使用它们,有时甚至可以节省属性查找(而不是使用hasattr)。编辑:为了澄清关于异常的一般观点。Paxdiablo在一般观点上是正确的。Python主要设计为“先问候再请求宽恕”,即先尝试看看会失败什么(异常),然后再“看着跳”看看有什么,然后应用。这是因为在Python中属性查找可能很昂贵,因此调用相同的东西(以检查边界)是浪费资源。但是,Python内部的东西通常有更好的帮助器,所以最好使用它们。

这个问题并不特定于字典,主要是为了展示。我想知道的是事先检查边界还是退回到异常。 - Dyno Fu
1
@Dyno:那你应该问“那个”。一个好的经验法则是“宁愿请求原谅,也不要征得许可”,但我会补充说,如果常见情况会导致频繁请求原谅,那么你应该先请求原谅。 - Alok Singhal
你的意思是“就这么做”吗?如果出了问题,那就之后再处理? - Dyno Fu
2
我想说“去做吧”,但是一定要确保你知道可能会出什么问题。不要捕捉所有的异常,只捕捉你希望发生的特定异常。如果你捕捉了所有东西,当发生严重问题时,由于没有反馈,很难找到问题所在。 - David Raznick

11

一般来说(不一定是针对Python),除了最简单的情况外,我倾向于使用“尝试并告诉我是否出错”的方法(异常)。这是因为在多线程环境或访问数据库期间,底层数据可以在键检查和值提取之间发生更改。

如果你没有在当前线程之外更改关联数组,则可以使用“先检查再提取”的方法。

但这是针对一般情况。在这里,具体而言,您可以使用get方法,该方法允许您指定一个默认值,以防键不存在:

return d.get (c, "N/A")
我会澄清我在第一段中所陈述的内容。当底层数据在检查和使用之间可能会发生改变时,您应该始终使用异常操作(除非您有一个不会引起问题的操作,例如上面提到的`d.get()`)。例如,考虑以下两个线程时间线:
+------------------------------+--------------------+
| Thread1                      | Thread2            |
+------------------------------+--------------------+
| Check is NY exists as a key. |                    |
|                              | Delete NY key/val. |
| Extract value for NY.        |                    |
+------------------------------+--------------------+

当线程1尝试提取值时,无论如何都会出现异常,因此您可以为可能性编写代码并删除初始检查。

关于数据库的评论也很相关,因为那是另一种底层数据可能会更改的情况。这就是为什么我倾向于使用原子SQL(如果可能的话),而不是像获取键列表然后使用单个语句处理它们这样的方法。


我总是称之为“先行动,后征求同意”和“三思而后行”。但我同意在Python中,追求的是宽恕。 - David Raznick
我倾向于将这个和jweede的答案都标记为最佳答案,但似乎这是不可行的。 - Dyno Fu

9

通常,异常会带来一些开销,只用于真正的“异常”情况。在这种情况下,这似乎是执行的正常部分,而不是“异常”或“错误”状态。

总的来说,我认为你的代码将受益于使用“if/else”约定,并仅在真正需要时保存异常。


2
这让我想起了"按照你的思路编写代码"和"设计越好,做得越好"。因此,我将其标记为答案。 - Dyno Fu
当你说异常会带来额外负担时,你可能是正确的,但 Python 的风格是“宁愿请求原谅,也不要事先获得许可”。这意味着你不需要检查某个东西是否存在。你可以尝试去做它,然后再请求原谅。如果是我,我会选择使用 try/except - Jeffrey Jose
2
那是一个很好的观点jeffjose。在Python中尝试并查看哪些方法有效确实是明智的。然而,在这种情况下使用try/except子句,当我们不期望任何异常时,这不是一个好主意。就像Richo所说:“坏的业力”。 :-) - Jon W

7
<不是。>
return d.get(c, 'N/A')

4
我赞同David的观点:
def f2(c):
   d = {
        "USA": "N.Y.",
        "China": "Shanghai"
       }

   return d.get(c, "N/A")

...这就是我会写的方式。

针对您的其他选项:

在'f1()'中,本质上没有什么问题,但字典有一个get()方法,几乎可以用于这种精确的用例:“从字典中获取此内容,如果不存在,则返回其他内容”。你的代码只是这样说的,使用get()更加简洁。

在'f2()'中,单独使用'except'是不受欢迎的,此外,您实际上没有对异常做出任何有用的响应--在您的情况下,调用代码将永远不知道存在异常。那么,如果它不为您的函数或调用它的代码增加价值,为什么要使用这个结构呢?


1

我看到人们使用“get”,这是我推荐的。但是,如果你将来发现自己处于类似的情况中,请捕获你想要的异常:

try:
    return d[k]
except KeyError:
    return "N/A"

这样,其他异常(包括KeyboardInterrupt)就不会被捕获。你几乎永远不想捕获KeyboardInterrupt


0

我认为在这种情况下,dict.get是最好的解决方案。

一般来说,我认为你的选择取决于异常出现的可能性。如果你预计键查找大多数情况下都会成功,那么在我看来,try/catch是更好的选择。同样地,如果它们经常失败,if语句更好。

在Python中,异常与属性查找的性能差异不大,因此我更担心使用异常/先行检查的逻辑而不是性能方面。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接