前言
Python与Golang中的“延迟绑定”主要出现在闭包中。
本文主要通过几个简单案例介绍一下Python中闭包的延迟绑定与Golang中闭包与Goroutine的延迟绑定机制与理解。
Python中闭包的延迟绑定
简单的案例
先来看一个使用Python实现闭包延迟绑定的简单案例:
def outer(): x = 1 def inner(): print(x) x = 123 return inner f = outer() f() # 123
我们可以看到,闭包inner在outer执行时记录了产生它的时候外部环境的所有环境(其实就是变量x),然后在执行闭包(执行f())的时候寻找外部环境最新的那个值(很显然,x的最新的值是123),所以程序最终会打印123!
这就是闭包十分神奇的地方:闭包会保存外部引用环境!(如果按照常规思路来理解,在执行inner时外部x变量的生命周期按理说已经结束,inner函数中没有x会报错...)
返回lambda匿名函数列表
def gen_func_list() -> list: # 匿名函数的输入是x与y return [lambda x, y: (x + y) * i for i in range(3)] # 注意返回的是匿名函数组成的列表 func_lst = gen_func_list() print("func_lst: ", func_lst) """ [<function gen_list.<locals>.<listcomp>.<lambda> at 0x7fe1622e9ea0>, <function gen_list.<locals>.<listcomp>.<lambda> at 0x7fe1622e9840>, <function gen_list.<locals>.<listcomp>.<lambda> at 0x7fe1622e9f28>] """ for func in func_lst: # 匿名函数需要2个参数 print(func(1, 2))
## 结果:
# 6
# 6
# 6
通过上面那个例子,这个案例的输出也十分容易理解了:闭包在执行时会寻找外部环境最新的值,很显然 for range循环最新的值时2,所以所有函数都会打印 (1+2)*2,结果是6。
Golang中闭包与Goroutine的延迟绑定
闭包的延迟绑定
与Python一样,我们先来看一个简单的案例:
package test1 import ( "fmt" "testing" ) // 闭包的延迟绑定 func fooClosure() func(){ x := 1 f := func(){ fmt.Printf("fooClosure val = %d ", x) } x = 123 return f } func TestClosure(t *testing.T){ f8 := fooClosure() f8() // fooClosure val = 123 }
与Python相同,Golang中的闭包也会保存外部环境,在闭包执行阶段会寻找外部环境最新的值处理。
与上面的返回lambda函数列表对应的一个例子:
package test1 import ( "fmt" "testing" ) // case7:闭包的延迟绑定 func foo7(x int) []func() { var fs []func() values := []int{1, 2, 3, 5} for _, val := range values { fs = append(fs, func() { fmt.Printf("foo7 val = %d ", x+val) }) } return fs } func TestFoo7(t *testing.T) { f7s := foo7(11) for _, f7 := range f7s { f7() } } /* foo7 val = 16 foo7 val = 16 foo7 val = 16 foo7 val = 16 */
Goroutine的延迟绑定
在Golang中,Goroutine也有延迟绑定的机制,我们来看看下面这个例子:
package test1 import ( "fmt" "sync" "testing" ) // goroutine的延迟绑定 func foo5(){ wait := sync.WaitGroup{} values := []int{1,2,3,5} for _, val := range values{ wait.Add(1) go func(){ wait.Done() fmt.Println("foo5 value = ", val) }() } // wait wait.Wait() } func TestFoo5(t *testing.T){ foo5() /* foo5 value = 5 foo5 value = 5 foo5 value = 5 foo5 value = 5 */ }
其实这个问题的本质同闭包的延迟绑定,或者说,这段匿名函数的对象就是闭包。在我们调用go func() { xxx }()
的时候,只要没有真正开始执行这段代码,那它还只是一段函数声明。而在这段匿名函数被执行的时候,才是内部变量寻找真正赋值的时候!
在上面的case中,for-range的遍历几乎是“瞬时完成的”,4个Go Routine真正被执行在其后。但是按照我们正常的逻辑来看:val的生命周期按理说已经结束了呀!程序不应该报错吗?
其实根据上面介绍的“闭包”的思路:goroutine并发执行的函数其实也是一个闭包!那么闭包在真正被执行的时候,即使for-range结束,但是在闭包中会保存外部环境val的值,并且每次都会使用最新的val,也就是5!