我最近在教自己Python并在代码执行之前发现了关于错误检查的LBYL / EAFP习语。在Python中,似乎接受的风格是EAFP,它似乎与语言一起使用。
LBYL( L ook B Y ou L EAP ):
def safe_divide_1(x, y):
if y == 0:
print "Divide-by-0 attempt detected"
return None
else:
return x/y
EAFP( E 更强 A sk F orgiveness而不是 P 错误):
def safe_divide_2(x, y):
try:
return x/y
except ZeroDivisionError:
print "Divide-by-0 attempt detected"
return None
我的问题是:我从未听说过使用EAFP作为主要数据验证结构,来自Java和C ++背景。 EAFP在Java中使用是否明智?或者是否存在过多的异常开销?我知道实际抛出异常时只有开销,所以我不确定为什么不使用更简单的EAFP方法。这只是偏好吗?
答案 0 :(得分:119)
如果您正在访问文件,EAFP比LBYL更可靠,因为LBYL中涉及的操作不是原子操作,文件系统可能会在您查看的时间和跳跃的时间之间发生变化。实际上,标准名称是TOCTOU - 检查时间,使用时间;由不准确的检查引起的错误是TOCTOU错误。
考虑创建一个必须具有唯一名称的临时文件。确定所选文件名是否存在的最佳方法是尝试创建它 - 确保使用选项以确保在文件已存在时操作失败(在POSIX / Unix术语中,O_EXCL标志为{{1 }})。如果您尝试测试文件是否已存在(可能使用open()
),那么在表示“否”的时间与您尝试创建文件的时间之间,某人或其他人可能已创建该文件。
相反,假设您尝试读取现有文件。您检查文件是否存在(LBYL)可能会说“它在那里”,但是当您实际打开它时,您会发现“它不存在”。
在这两种情况下,你必须检查最后的操作 - 而LBYL并没有自动帮助。
(如果你正在搞乱SUID或SGID程序,access()
会提出一个不同的问题;它可能与LBYL有关,但代码仍然必须考虑失败的可能性。)
答案 1 :(得分:46)
除了Python和Java中异常的相对成本之外,请记住它们之间的哲学/态度存在差异。 Java试图对类型(以及其他所有内容)非常严格,要求对类/方法签名进行明确,详细的声明。它假定您应该随时知道您正在使用的对象类型以及它能够执行的操作。相比之下,Python的“鸭子打字”意味着你不确定(并且不应该关心)对象的清单类型是什么,你只需要关心它在你提出要求时嘎嘎叫。在这种宽松的环境中,唯一理智的态度就是假定事情会起作用,但如果不这样做,就要准备好应对后果。 Java的自然限制性并不适合这种随意的方法。 (这并不是要贬低任何一种方法或语言,而是说这些态度是每种语言成语的一部分,而在不同语言之间复制习语往往会导致尴尬和沟通不畅......)
答案 2 :(得分:10)
Python中的异常处理比Java更有效,至少部分为什么你在Python中看到了这个构造。在Java中,以这种方式使用异常的效率更低(在性能方面)。
答案 3 :(得分:7)
就个人而言,我认为这得到了大会的支持,EAFP永远不是一个好方法。 您可以将其视为以下内容的等效内容:
if (o != null)
o.doSomething();
else
// handle
而不是:
try {
o.doSomething()
}
catch (NullPointerException npe) {
// handle
}
此外,请考虑以下事项:
if (a != null)
if (b != null)
if (c != null)
a.getB().getC().doSomething();
else
// handle c null
else
// handle b null
else
// handle a null
这可能看起来不那么优雅(是的,这是一个粗略的例子 - 与我一起承担),但它在处理错误方面给你更大的粒度,而不是将它全部包装在try-catch中以获得NullPointerException
,然后尝试找出你得到它的地点和原因。
我认为不应该使用EAFP,除了极少数情况。此外,由于您提出了问题:是的,即使没有抛出异常,try-catch块也会产生一些开销。
答案 4 :(得分:5)
请考虑以下代码段:
def int_or_default(x, default=0):
if x.isdigit():
return int(x)
else:
return default
def int_or_default(x, default=0):
try:
return int(x)
except ValueError:
return default
他们看起来都正确,对吗?但其中一个不是。
使用LBYL的前者由于isdigit
和isdecimal
之间的微妙区别而失败;当使用字符串“①²³5”调用时,它将抛出错误而不是正确返回默认值。
根据定义,后者使用EAFTP可以实现正确的处理。没有行为不匹配的余地,因为需要的代码是断言该要求的代码。
使用LBYL意味着获取内部逻辑并将其复制到每个呼叫站点。而不是对您的要求进行一次规范编码,您可以在每次调用函数时都有机会搞乱。
值得注意的是,EAFTP 不是关于异常,而Java代码尤其不应该普遍使用异常。它是关于为正确的代码块提供正确的工作。例如,使用Optional
返回值是编写EAFTP代码的完全有效的方法,并且比LBYL更有效地确保正确性。