73

我最近在自学 Python,并发现了关于代码执行前错误检查的 LBYL/EAFP 习语。在 Python 中,似乎公认的风格是 EAFP,而且它似乎与该语言很好地配合。

LBYL 之前看看_

def safe_divide_1(x, y):
    if y == 0:
        print "Divide-by-0 attempt detected"
        return None
    else:
        return x/y

EAFP Ask F orgivenessPermission容易

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 方法。只是偏好吗?

4

5 回答 5

139

如果你正在访问文件,EAFP 比 LBYL 更可靠,因为 LBYL 中涉及的操作不是原子的,文件系统可能会在你查看的时间和跳跃的时间之间发生变化。实际上,标准名称是TOCTOU——检查时间,使用时间;检查不准确导致的错误是 TOCTOU 错误。

考虑创建一个必须具有唯一名称的临时文件。找出所选文件名是否存在的最佳方法是尝试创建它 - 确保使用选项来确保如果文件已经存在则操作失败(在 POSIX/Unix 术语中,O_EXCL 标志为open())。如果您尝试测试文件是否已经存在(可能使用access()),那么在显示“否”的时间和您尝试创建文件的时间之间,可能有人或其他人创建了该文件。

相反,假设您尝试读取现有文件。您检查文件是否存在 (LBYL) 可能会说“它在那里”,但是当您实际打开它时,您会发现“它不存在”。

在这两种情况下,您都必须检查最终操作 - LBYL 不会自动提供帮助。

(如果你在弄乱 SUID 或 SGID 程序,access()问一个不同的问题;它可能与 LBYL 相关,但代码仍然必须考虑失败的可能性。)

于 2009-01-01T17:52:24.157 回答
55

除了 Python 和 Java 中异常的相对成本之外,请记住它们之间的理念/态度存在差异。Java 试图对类型(以及其他所有内容)非常严格,需要明确、详细地声明类/方法签名。它假定您在任何时候都应该确切地知道您正在使用什么类型的对象以及它能够做什么。相比之下,Python 的“鸭子类型”意味着您不确定(也不应该关心)对象的清单类型是什么,您只需要关心它在您要求它时会发出嘎嘎声。在这种放任自流的环境中,唯一理智的态度就是假设事情会奏效,但如果不奏效,就要准备好应对后果。Java 的自然限制性 不适合这种随意的方法。(这并不是要贬低方法或语言,而是说这些态度是每种语言习语的一部分,在不同语言之间复制习语往往会导致尴尬和沟通不畅……)

于 2009-01-02T23:31:52.107 回答
12

在 Python 中处理异常比在 Java 中更有效,这至少部分是您在 Python 中看到该结构的原因。在 Java 中,以这种方式使用异常会更加低效(在性能方面)。

于 2009-01-01T14:30:19.043 回答
10

考虑这些代码片段:

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 更有效。

于 2017-04-28T18:26:54.340 回答
9

就个人而言,我认为这得到了惯例的支持,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 块也会产生一些开销。

于 2009-01-01T09:57:00.047 回答