关于编码风格:为什么在Python中“更容易请求宽恕而不是获得许可”?

Why is it “easier to ask forgiveness than it is to get permission” in Python?

为什么"请求宽恕比获得许可更容易"(EAFP)在Python中被认为是良好的实践?作为一个编程新手,我有这样的印象:与使用其他检查相比,使用许多try...except例程将导致代码膨胀和不可读。

EAFP方法的优势是什么?

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


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

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

EAFP示例:

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

LBYL示例:

1
2
3
4
if 'snake' in zoo:
    snake = zoo['snake']
else:
    snake = None


这里混合了两种情况:断言和基于EAFP的逻辑。

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

在Python中,通常避免过多地使用断言。通常,您应该期望代码的用户正确地使用它。例如,如果您记录一个函数来接受一个不是None的参数,那么就不需要有一个断言来验证它。相反,只是期望有一个值。如果由于无值而出现错误,那么它将无论如何冒泡,因此用户知道他们做错了什么。但你不应该总是检查每件事。

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

1
2
3
4
5
6
7
8
9
10
11
# LBYL
if key in dic:
    print(dic[key])
else:
    handleError()

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

现在看起来非常相似,尽管您应该记住,lbyl解决方案会检查字典两次。与捕获异常的所有代码一样,只有当键的不存在是异常情况时,才应该这样做。因此,如果通常所提供的密钥被排除在字典中,那么它是EAFP,您应该直接访问它。如果您不希望密钥出现在字典中,那么您应该首先检查它的存在性(尽管异常在Python中比较便宜,但它们仍然不是免费的,因此在特殊情况下保留它们)。

EAFP的一个好处是,在库或应用程序的逻辑中,如果key来自上面,那么您可以假设在这里传递了一个有效的密钥。因此,您不需要在这里捕获异常,只需让它们在代码中冒泡到更高的点,然后您就可以处理错误了。这样,您就可以完全不受这些检查的影响而拥有较低级别的功能。


问得好!StackOverflow中很少有关于"原则背后的哲学"的问题。

关于python词汇表中的EAFP定义,我甚至进一步说,它提到的"如果假设被证明是错误的,则缓存异常"在这个上下文中有些误导。因为,让我们面对现实吧,下面的第二个代码片段看起来并不"干净和快速"(在前面的定义中使用的术语)。难怪手术室问了这个问题。

1
2
3
4
5
6
7
8
9
10
11
# LBYL
if key in dic:
    print(dic[key])
else:
    handleError()

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

我想说,EAFP发光的真正时刻是,您根本就不写try ... except ...,至少在大多数底层代码库中不写。因为,异常处理的第一条规则是:不要做异常处理。考虑到这一点,现在让我们将第二个片段改写为:

1
2
# Real EAFP
print(dic[key])

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