浅谈Python与Golang中的“延迟绑定机制”

前言

  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!

参考文章

https://zhuanlan.zhihu.com/p/92634505

原文地址:https://www.cnblogs.com/paulwhw/p/14487464.html