延迟函数在Go中是如何使用的,以及在其他语言中有哪些替代方法?

时间:2017-10-09 08:47:50

标签: python go deferred

我知道推迟一个过程。我还阅读了在处理文件和其他资源时使用它的好例子。我的问题是,它是不是像Python等其他语言处理的东西?如果是,Go如何提供一些独特的功能?

假设有两个函数f1f2,我想在f2完成时调用f1。我可以这样做:

def f1():
  done = False
  print('I am doing extensive work')
  done = True
  return done

def f2():
  print('here we go')

if f1():
  f2()

通过尽量减少编码工作,推迟做同样的事情吗?

3 个答案:

答案 0 :(得分:2)

defer顾名思义,将延迟执行延迟函数,直到调用函数体退出,无论退出点和条件。因此,即使f1内的某些内容因异常而失败,您的f2 deffering f2函数也会确保调用f1。我认为它是try / catch块中的finally子句。

在python中,我能想到的最接近defer的{​​{3}}是context managers。注意我怎么说最接近而且不相同,因为他们的目的有点重叠,但只有一点

答案 1 :(得分:1)

Go {'} defer statement只是另一种方法,它有其优点和缺点(不一定是最好的)。其他语言确实提供了处理适当资源关闭的替代方案例如,Java提供了例外和try-catch块。

引自Go FAQ: Why does Go not have exceptions?

  

我们认为将异常耦合到控制结构(如try-catch-finally惯用法)会导致错综复杂的代码。它还倾向于鼓励程序员标记太多普通错误,例如无法打开文件,这是特殊的。

     

Go采用不同的方法。对于简单的错误处理,Go的多值返回可以轻松报告错误而不会使返回值超载。 A canonical error type, coupled with Go's other features,使错误处理变得愉快,但与其他语言完全不同。

     Go还有一些内置函数可以从真正特殊的条件发出信号并从中恢复。恢复机制仅作为在错误之后被拆除的功能状态的一部分执行,这足以处理灾难但不需要额外的控制结构,并且当使用良好时,可以产生干净的错误处理代码。

     

有关详细信息,请参阅Defer, Panic, and Recover文章。

与Java defer相比,使用try-catch的优势是,处理已关闭或处置已打开资源的代码是紧邻代码旁边的代码打开它

例如:

f, err := os.Open("file.txt")
if err != nil {
    log.Printf("Failed to open file: %v", err)
    return
}
defer f.Close()

// f is open you may read from it
...

如果处理资源的其他语言被移动到另一个代码块,那么在实际处理它的情况下更难以遵循它。

答案 2 :(得分:1)

要理解延迟,您需要了解替代方案。

在处理许多资源的复杂代码中,你最终会做这样的事情(伪代码):

mtx.Lock()
file1, err := os.Open(...)
if err != nil {
    mtx.Unlock()
    return err
}
conn, err := net.Dial(...)
if err != nil {
    file1.Close()
    mtx.Unlock()
    return err
}
file2, err := os.Open(...)
if err != nil {
    conn.Close()
    file1.Close()
    mtx.Unlock()
    return err
}
do_things()
file2.Close()
conn.Close()
file1.Close()
mtx.Unlock()
return nil

这速度很快。此外,从长远来看,它很容易出错,因为有人编辑代码并且可能在打开file1之前将网络连接的开放移动可能无法正确更新所有错误处理路径。

在C中,这里常见的习惯是在函数末尾收集所有资源的清理,并执行以下操作:

    ret = ERROR;
    lock(mtx);
    fd1 = open(...);
    if (fd1 == -1)
        goto err1;
    conn = net_conn(...);
    if (conn == -1)
        goto err2;
    fd2 = open(...);
    if (fd2 == -1)
        goto err3;
    do_things();

    ret = 0;
    close(fd2);
err3:
    close(conn);
err2:
    close(fd1);
err1:
    unlock(mtx);
    return ret;

这也不是很好,也很容易弄错。

作为比较做同样的事情我们得到:

mtx.Lock()
defer mtx.Unlock()
file1, err := os.Open(...)
if err != nil {
    return err
}
defer file1.Close()
conn, err := net.Dial(...)
if err != nil {
    return err
}
defer conn.Close()
file2, err := os.Open(...)
if err != nil {
    return err
}
defer file2.Close()
.... do things ...
return nil

清楚,简洁,任何了解Go的人都会理解会发生什么,并且很难在将来出错或打破。