【问题标题】:Why is it "easier to ask forgiveness than it is to get permission" in Python?为什么在 Python 中“请求宽恕比获得许可更容易”?
【发布时间】:2015-10-02 06:50:34
【问题描述】:

为什么“请求宽恕比获得许可更容易”(EAFP)被认为是 Python 中的好习惯?作为一个编程新手,我的印象是,与使用其他检查相比,使用许多try...except 例程会导致代码臃肿且可读性降低。

EAFP 方法的优势是什么?

注意:我知道这里有类似的问题,但它们大多是指一些具体的例子,而我对原理背后的哲学更感兴趣。

【问题讨论】:

  • 断言不适用于代码流。
  • @IgnacioVazquez-Abrams 你能详细说明一下吗?据我了解,它们是try...except 的替代品?我应该换个问题吗?
  • 它们应该用于确保您的程序在梨形情况下不会做任何愚蠢的事情,而不是用于检查是否有人传递了整数而不是字符串。
  • 好的 - 我删除了 assert。显然我过去没有正确使用这些......

标签: python coding-style


【解决方案1】:

LBYLEAFP 的计数器方法与断言没有任何关系,它只是意味着您在尝试访问可能不存在的东西之前添加一个检查。

Python 是 EAFP 的原因在于,与其他语言(例如 Java)不同 - 在 Python 中,捕获异常是一种相对便宜的操作,这就是鼓励您使用它的原因。

EAFP 示例:

try:
    snake = zoo['snake']
except KeyError as e:
    print "There's no snake in the zoo"
    snake = None

LBYL 示例:

if 'snake' in zoo:
    snake = zoo['snake']
else:
    snake = None

【讨论】:

  • 请注意,第二个应该使用唯一的哨兵对象,否则它将在任何错误值上失败。
  • @IgnacioVazquez-Abrams 你是对的,但在这个例子中我想我可以安全地假设False0 或任何其他虚假值不会代表动物园里的蛇 :)
  • @brunodesthuilliers 你认为抛出一百万个异常并捕获它们的例子是一个很好的例子,可以用作“正常”程序的模型吗? :)
  • @brunodesthuilliers 正如我在回答中试图解释的那样,与其他语言相比,在 Python 中捕获异常更便宜,但它当然不是免费的。例外情况仍然是例外情况,在这种情况下,如果您希望密钥大部分时间都存在。然后,对 LBYL 的额外检查会稍微贵一些。
  • @brunodesthuilliers 但它一种廉价的操作。这并不意味着您可以将它与其他更便宜的东西进行比较并说它是错误的。这就像说print 很贵,因为不打印会便宜很多。尝试在例如运行相同的示例Java,您可以在这里看到为什么异常便宜。
【解决方案2】:

您在这里混合了两件事:断言和基于 EAFP 的逻辑。

断言用于验证函数的契约,即它的前置条件和后置条件,有时还有它的不变量。他们确保以应有的方式使用功能。但它们不适用于代码流,因为它们会在出错时完全中断执行。一个常见的例子是检查函数调用中的None 参数。

在 Python 中,您通常会避免过多地使用断言。一般来说,您应该期望您的代码的用户正确使用它。例如,如果您记录一个函数以获取不是None 的参数,那么就没有必要有一个断言来验证它。相反,只是期望有一个值。如果因为 None 值而出现错误,那么无论如何它都会冒泡,所以用户知道他们做错了什么。但是您不必一直检查所有内容。

现在,EAFP 有所不同。它用于控制流,或者更确切地说,它避免了额外的控制流,有利于期望事情是正确的,如果不正确则捕获异常。显示差异的一个常见示例是字典中的键访问:

# LBYL
if key in dic:
    print(dic[key])
else:
    handleError()

# EAFP
try:
    print(dic[key])
except KeyError:
    handleError()

现在看起来非常相似,尽管您应该记住 LBYL 解决方案检查字典两次。与所有捕获异常的代码一样,只有在键不存在是异常情况时才应该这样做。因此,如果通常提供的键不在字典中,那么它就是 EAFP,您应该直接访问它。如果您不希望密钥出现在字典中,那么您可能应该首先检查它的存在(虽然 Python 中的异常更便宜,但它们仍然不是免费的,因此请保留它们以备不时之需)。

这里 EAFP 的一个好处还在于,在您的库或应用程序逻辑的更深处,key 来自上面,您可以假设在这里传递了一个有效的密钥。所以你不需要在这里捕获异常,只需让它们冒泡到代码中的更高点,然后你可以处理错误。这使您可以拥有完全没有此类检查的较低级别的功能。

【讨论】:

  • 谢谢。 Big +1 用于说明和解释 Python 中 EAFP 方法的不同优势。
【解决方案3】:

好问题! StackOverflow 中很少有关于“原理背后的哲学”的问题。

关于EAFP definition in Python glossary,我什至会说它提到“如果假设被证明是错误的,则缓存异常”在这种情况下有些误导。因为,让我们面对现实吧,下面的第二个代码 sn-p 看起来并不“干净和快速”(上述定义中使用的术语)。难怪OP会问这个问题。

# LBYL
if key in dic:
    print(dic[key])
else:
    handleError()

# EAFP
try:
    print(dic[key])
except KeyError:
    handleError()

我想说,EAFP 真正闪耀的时刻,是你根本不写try ... except ...,至少在你的大部分底层代码库中都没有。因为,the first rule of exception handling: do not do exception handling。考虑到这一点,现在让我们将第二个 sn-p 重写为:

# Real EAFP
print(dic[key])

现在,真正的 EAFP 方法不是既干净又快速吗?

【讨论】:

    【解决方案4】:

    我将扩展@RayLuo 的答案。

    LBYL 的问题是,它实际上通常不起作用。除非你有一个单线程的应用程序,否则总会有可能的竞态条件:

    # LBYL
    if key in dic:
        # RACE CONDITION
        print(dic[key])
    else:
        handleError()
    
    # EAFP
    try:
        print(dic[key])
    except KeyError:
        handleError()
    

    可以在if 检查和print 之间的字典中添加一个键。在这种特殊情况下,这听起来不太可能。但是,如果您进行的检查是针对数据库、API 或任何可以与您的应用程序数据异步更改的外部数据源,则更有可能发生这种情况。

    这意味着实现 LBYL 的“正确”方式是:

    if key in dic:
        try:
            print(dic[key])
        except KeyError:
            handleError()
    else:
        handleError()
    

    注意try / except 子句与 EAFP 方法。

    由于即使在使用 LBYL 方法时也必须处理 EAFP 样式的异常,所以最好首先使用 EAFP 方法。

    我使用if 检查的唯一情况是后续操作(在本例中为print)是否非常昂贵/耗时。这种情况很少见,也不是每次都使用if 检查的理由。

    底线:LBYL 在一般情况下不起作用,但 EAFP 可以。优秀的开发人员专注于他们可以自信地用于解决各种问题的通用解决方案模式。学习始终如一地使用 EAFP。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-29
      • 2020-08-02
      • 1970-01-01
      • 2020-08-24
      • 1970-01-01
      • 2020-06-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多