我最近在教自己Python并在代码执行之前发现了关于错误检查的LBYL/EAFP习语.在Python中,似乎接受的样式是EAFP,它似乎与该语言一起使用.
LBYL(大号 OOK 乙安伏ý OU 大号 EAP):
def safe_divide_1(x, y): if y == 0: print "Divide-by-0 attempt detected" return None else: return x/y
EAFP(它的ê asier到一个 SK ˚F orgiveness比P ermission):
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方法.这只是偏好吗?
如果您正在访问文件,EAFP比LBYL更可靠,因为LBYL中涉及的操作不是原子操作,并且文件系统可能会在您查看的时间和跳跃的时间之间发生变化.实际上,标准名称是TOCTOU - 检查时间,使用时间; 由不准确的检查引起的错误是TOCTOU错误.
考虑创建一个必须具有唯一名称的临时文件.找出所选文件名是否存在的最佳方法是尝试创建它 - 确保使用选项以确保如果文件已存在则操作失败(在POSIX/Unix术语中,O_EXCL标志为open()
).如果您尝试测试文件是否已存在(可能正在使用access()
),那么在说"否"的时间与您尝试创建文件的时间之间,某人或其他人可能已创建该文件.
相反,假设您尝试读取现有文件.您检查文件是否存在(LBYL)可能会说"它在那里",但是当您实际打开它时,您会发现"它不存在".
在这两种情况下,您都必须检查最终操作 - 并且LBYL没有自动帮助.
(如果你搞乱了SUID或SGID程序,access()
问一个不同的问题;它可能与LBYL有关,但代码仍然必须考虑失败的可能性.)
除了Python和Java中异常的相对成本之外,请记住它们之间的哲学/态度存在差异.Java试图对类型(以及其他所有内容)非常严格,要求对类/方法签名进行明确,详细的声明.它假定您应该随时知道您正在使用的对象类型以及它能够执行的操作.相比之下,Python的"鸭子打字"意味着你不确定(并且不应该关心)对象的清单类型是什么,你只需要关心它在你提出要求时嘎嘎叫.在这种宽松的环境中,唯一理智的态度就是假定事情会起作用,但如果不这样做,就要准备好应对后果.Java的自然限制性没有' 这种随意的方法非常适合.(这并不是为了贬低任何一种方法或语言,而是说这些态度是每种语言成语的一部分,而在不同语言之间复制习语往往会导致尴尬和沟通不畅......)
Python中的异常处理比Java更有效,这至少部分是为什么你在Python中看到这个构造.在Java中,以这种方式使用异常在效率方面(在性能方面)效率更低.
就个人而言,我认为这是通过惯例支持的,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块也会产生一些开销.
考虑以下代码片段:
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
。当使用字符串“?²³??”调用时,它将引发错误,而不是正确返回默认值。
根据定义,使用EAFTP的后者会导致正确的处理。没有余地行为不匹配,因为需要要求的代码是断言要求的代码。
使用LBYL意味着获取内部逻辑并将其复制到每个呼叫站点。不必对需求进行规范的编码,而是有机会在每次调用函数时弄乱自己。
值得一提的是,EAFTP 不是有关异常,和Java代码尤其是不应该被普遍地使用异常。它是为正确的代码块提供正确的工作。例如,使用Optional
返回值是编写EAFTP代码的一种完全有效的方法,并且比LBYL更有效地确保了正确性。