zoukankan      html  css  js  c++  java
  • 浅谈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

  • 相关阅读:
    Web性能优化系列(3):如何延迟加载JS
    Web性能优化系列(2):剖析页面绘制时间
    Web性能优化系列(1):Web性能优化分析
    页面制作之开发调试工具(1)
    格式化 SQL 来提高效率
    关于SQL注入,你应该知道的那些事
    jQuery()方法的第二个参数
    JSON简介以及用法代码汇总
    js简单的面试题
    常用meta整理
  • 原文地址:https://www.cnblogs.com/paulwhw/p/14487464.html
Copyright © 2011-2022 走看看