Why is it “easier to ask forgiveness than it is to get permission” in Python?
为什么"请求宽恕比获得许可更容易"(EAFP)在Python中被认为是良好的实践?作为一个编程新手,我有这样的印象:与使用其他检查相比,使用许多try...except例程将导致代码膨胀和不可读。
EAFP方法的优势是什么?
注意:我知道这里也有类似的问题,但它们大多是指一些具体的例子,而我更感兴趣的是这个原则背后的哲学。
- 断言不适用于代码流。
- @艾布拉姆斯,你能详细说明一下吗?据我所知,它们是try...except的替代品?我应该改变这个问题吗?
- 它们应该用于确保程序在梨形的情况下不会做任何愚蠢的事情,而不是检查是否有人传递了整数而不是字符串。
- 好的-我取下了assert。很明显我以前没有正确地使用那些…
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 |
号
- 请注意,第二个对象应该使用唯一的sentinel对象,否则它将在任何错误值上失败。
- @伊格纳西奥瓦兹奎兹·艾布拉姆斯,你是对的,但在这个例子中,我认为我可以安全地假设,False、0或任何其他错误的值不会代表动物园中的蛇:)
- 如果您使用的是dict.get,那么您只需将None作为其默认值:zoo.get('snake', None)。这对LBYL来说并不是一个很好的例子,因为它使用了原谅dict.get。
- @说真的,它可以缩写为:snake = zoo.get('snake', None),但重点是展示一个lbyl的例子,而不是如何"围绕它工作":)
- 然后做正确的"查找"部分,检查字典中是否存在该键,而不宽容地从中获取值,并检查该值的真实性。
- @当然,谢谢你的反馈。
- "Python是EAFP的原因是不同于其他语言(例如Java)——在Python中捕获异常是一种廉价的操作":嗯,这是错误的。设置try/except块很便宜,但捕获异常却不便宜。尝试使用此代码来检查gist.github.com/realbigb/38f4579c543261f1400f-这里(python 2.7.x)EAFP为1.3156080246,LBYL为0.368113994598。
- @brunodesthuilliers您是否考虑过一个抛出一百万个异常并捕获它们的示例,它可以用作"正常"程序的模型?:)
- @我在回答中试图解释的是,在Python中捕获异常比其他语言便宜,但当然不是免费的。例外情况仍然适用于例外情况,在这种情况下,如果您希望密钥大部分时间都存在的话。然后,对lbyl的额外检查稍微贵一些。
- @brunodesthuilliers举个例子:(1.3156080246-0.368113994598)/1000000=0.00000094749403,这是在您给出的示例中捕获一个异常的平均成本。这不到一微秒,或947纳秒,如果要更准确…
- @alfasin wrt/代码只是您自己的例子——并且声明"在python中捕获异常是一个便宜的操作"显然是误导性的。它可能比其他一些语言便宜,但它仍然比键查找贵得多。
- @早午餐。你把我的话从上下文中删去了,与其他语言相比,这是不贵的,因此鼓励使用。我没有说它是"免费的"。
- @但这是一个便宜的手术。这并不意味着你可以把它和其他更便宜的东西比较,说它是错的。这就像是说print很贵,因为不打印要便宜得多。试着在Java中运行同一个例子,你可以看到为什么这里的异常是便宜的。
- 有一次,我不得不为一项复杂的日常计算任务修改别人的代码,这项任务耗费了太多时间。他到处用EAFP。把它改成lbyl几乎使执行时间缩短了一半。在上面的例子中,EAFP版本的速度只慢了3.5倍,是的,当然这很便宜。这将是我对此的最后评论。
- @Brunodesthuilliers这个"3.5x慢"使它每百万次执行速度慢1秒。当你"改进"他的代码时,你一定做了其他你甚至不知道的事情…
这里混合了两种情况:断言和基于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来自上面,那么您可以假设在这里传递了一个有效的密钥。因此,您不需要在这里捕获异常,只需让它们在代码中冒泡到更高的点,然后您就可以处理错误了。这样,您就可以完全不受这些检查的影响而拥有较低级别的功能。
- 谢谢您。Big+1用于说明和解释Python中EAFP方法的不同优势。
问得好!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方法干净快速吗?