python golang 小工具_使用Go语言简单模拟Python的生成器

def demo_input_and_output():

input = yield 'what is the input?'

yield 'input is: %s' % input

gen = demo_input_and_output()

print(gen.next())

print(gen.send(42))

这段代码演示了 python generator 的功能。可以看到 yield 同时做了两个操作,一个是往外发数据 "waht is the input",同时做的操作是往里收数据 input。而且这个接收数据的操作是一个阻塞的操作,如果外部没有调用 next() (也就是往里传递None),或者调用send(42)(也就是往里传递42这个值),那么这个阻塞的操作就会一直等待下去。

也就是说 python 的 generator 自带了一个对外通信的 channel,用于收发消息。用 go 模拟 python 的 generator 的话写起来就是这样的

package main

import "fmt"

func demoInputAndOutput(channel chan string) {

channel <- "what is my input?"

input := <- channel

channel <- fmt.Sprintf("input is: %s", input)

}

func main() {

channel := make(chan string)

go demoInputAndOutput(channel)

fmt.Println(<- channel)

channel <- "42"

fmt.Println(<- channel)

}

这段代码和 python 版本基本上等价。隐含的 channel 在 go 版本里变成显式的了。yield 变成了 channel <- 操作,同时立马做了一个 <- channel 的阻塞读操作。这也就是 yield 的本质吧。

go 的 channel 也可以当成 iterator 被 for 循环使用:

package main

import "fmt"

func someGenerator() <-chan string {

channel := make(chan string)

go func() {

channel <- "a"

fmt.Println("after a")

channel <- "c"

fmt.Println("after c")

channel <- "b"

fmt.Println("after b")

close(channel)

}()

return channel

}

func main() {

channel := someGenerator()

for val := range channel {

fmt.Println(val)

}

}

和 python 的 yield 不同,这里的 channel <- 不等价于 yield,它会往下执行直到阻塞。效果是

after a

a

c

after c

after b

b

这和预期的顺序不一样。这里没有把 after a after c after b 都打印出来是因为 channel 默认只有一个元素的buffer,所以写入了一个就阻塞了。如果增大 buffer,那么就有效果了

make(chan string, 10)

输出变成了:

after a

after c

after b

a

c

b

可见 goroutine 就好象一个独立的线程一样自己和自己玩去了,不用等待被执行。如果要模拟 yield 就要加上显示的同步操作(从 channel 里阻塞读取信号):

package main

import "fmt"

func someGenerator() chan string {

channel := make(chan string)

go func() {

channel <- "a"

<- channel

fmt.Println("after a")

channel <- "c"

<- channel

fmt.Println("after c")

channel <- "b"

<- channel

fmt.Println("after b")

close(channel)

}()

return channel

}

func main() {

channel := someGenerator()

for val := range channel {

fmt.Println(val)

channel <- ""

}

}

输出的结果就是

a

after a

c

after c

b

after b

到这里我们可以看到,python 的 generator 就好象是 golang 的 goroutine 带了一个无buffer的channel。这样导致每次yield一个值,都会产生一次协程上下文切换。虽然协程上下文切换很廉价,但是也不是没有成本。像 goroutine 的 buffered channel 这样的设计,可以让一个 goroutine 一次性多产生一些输出再阻塞等待,而不是产生一个输出就阻塞等待一下,再产生另外一个输出。golang rocks!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值