闭包是高级语言通常都会包含的一种语法,Python和Go中都支持这个语法。
那么,究竟什么是闭包呢?
首先我们从匿名函数开始讲起:
匿名函数
同样Python和Go都支持匿名函数,以Go为例,我们看一个简单的例子吧:
package main
import "fmt"
func main() {
lambda := func() {
fmt.Println("I'm an anonymous function")
}
lambda()
}
运行结果:
➜ closure git:(master) ✗ go run lambda.go
I'm an anonymous function
函数作为一等公民(First class citizen),可以把它赋给一个变量,这样就可以做到实时更新:
package main
import "fmt"
func main() {
var functor func() int
functor = func() int {
return 10
}
fmt.Println(functor())
functor = func() int {
return 15
}
fmt.Println(functor())
functor = func() int {
return 20
}
fmt.Println(functor())
}
运行结果:
➜ closure git:(master) ✗ go run functor_realtime_update.go
10
15
20
闭包
如果一个内部嵌套函数中,引用了外部定义的变量,那么我们就称这个函数为“闭包”,同时我们称被引用的变量为“引用变量”或“自由变量”。
下面我们看一个引用外部变量的嵌套函数:
package main
import "fmt"
func main() {
var nestedFunctorInMain func()
counter := 0
nestedFunctorInMain = func() {
counter += 1
fmt.Println(counter)
}
nestedFunctorInMain()
nestedFunctorInMain()
nestedFunctorInMain()
}
运行结果:
➜ closure git:(master) ✗ go run nested_function.go
1
2
3
这里的“nestedFunctorInMain”就是一个闭包,它嵌套在入口函数“main”中,并且引用了外部变量“counter”。
那闭包有什么特殊的用处呢?最简单的,利用闭包特性,我们可以达到数据隔离的目的:
package main
import "fmt"
func main() {
counter := newCounter()
fmt.Println(counter())
fmt.Println(counter())
}
func newCounter() func() int {
n := 0
return func() int {
n += 1
return n
}
}
运行结果:
➜ closure git:(master) ✗ go run data_isolation.go
1
2
之前的嵌套函数的例子里面,我们的闭包使用了main函数中的外部变量,但是这个变量使用范围过大,其他函数也可以更新它,在本例中,我们设置在新函数中设置变量“n”,而嵌套匿名函数引用它,即使“newCounter”函数执行完毕,变量依然存在闭包中,且外部无法访问,这样就很方便地达到了数据隔离的目的。
闭包中遇到的常见问题
- defer和go使用函数调用作为参数
假设我们有个setup和teardown的功能需要实现,即在函数执行前利用setup做一些初始化操作,然后在执行结束后,做环境清理工作:
package main
import "fmt"
func main() {
defer func() {
fmt.Prinltn("teardown")
}
}
运行结果:
➜ closure git:(master) ✗ go run defer_need_func_call.go
# command-line-arguments
./defer_need_func_call.go:3:8: imported and not used: "fmt"
./defer_need_func_call.go:8:4: expression in defer must be function call
因为“defer”和“go”关键字的参数是函数调用,而不是函数,所以按照本例的写法,编译代码时会报错:“expression in defer must be function call”。
我们改进一下:
package main
import "fmt"
func main() {
defer setup()
}
func setup() func() {
fmt.Println("pretend to set things up")
return func() {
fmt.Println("pretend to tear things down")
}
}
运行结果:
➜ closure git:(master) ✗ go run defer_need_func_call.go
pretend to set things up
“teardown”并未得到执行!为什么?
仔细看本例中代码,“setup”函数返回匿名函数,即“teardown”,但是“teardown”并未有机会得到显式执行,所以代码未运行到。
再改进一下:
package main
import "fmt"
func main() {
defer setup()()
}
func setup() func() {
fmt.Println("pretend to set things up")
return func() {
fmt.Println("pretend to tear things down")
}
}
运行结果:
➜ closure git:(master) ✗ go run defer_need_func_call.go
pretend to set things up
pretend to tear things down
显式执行返回的匿名函数,“teardown”部分得到执行。
- “for”循环中的变量是引用传递而非值传递
这个问题很容易让人迷惑,我们先看个例子:
package main
import "fmt"
func main() {
var functions []func()
for i := 0; i < 10; i++ {
functions = append(functions, func() {
fmt.Println(i)
})
}
for _, f := range functions {
f()
}
}
运行结果:
➜ closure git:(master) ✗ go run for_loop_refer_assign.go
10
10
10
10
10
10
10
10
10
10
按照我们在理解,不是应该按顺序输出“0”到“9”的数字吗?这是因为,当我们在“for”循环做迭代的时候,“for”内部的变量传递不是我们通常理解的值传递,而是引用传递,导致最后函数切片中所有的函数中使用到的变量“i”都是循环之后变量“i”的值,从而最后输出的都是“10”。
那如果我们想按照初衷一次打印每个迭代中的数字,如何做呢?
有一种办法可以做到,因为函数参数是值传递,可以通过一个辅助函数达到目的:
package main
import "fmt"
func main() {
var functions []func()
for i := 0; i < 10; i++ {
functions = append(functions, build(i))
}
for _, f := range functions {
f()
}
}
func build(val int) func() {
return func() {
fmt.Println(val)
}
}
运行结果:
➜ closure git:(master) ✗ go run for_loop_refer_assign.go
0
1
2
3
4
5
6
7
8
9