使用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!

(0)

相关推荐

  • go语言返回1-99之间随机数的方法

    本文实例讲述了go语言返回1-99之间随机数的方法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: package main import (     "fmt"     "math/rand" ) func main() {     max := big.NewInt(100)     i, err := rand.Int(rand.Reader, max) } 希望本文所述对大家的Go语言程序设计有所帮助.

  • 理解Golang中的数组(array)、切片(slice)和map

    我比较喜欢先给出代码,然后得出结论 数组 复制代码 代码如下: package main import (     "fmt" ) func main() {     arr := [...]int{1, 2, 3}     //打印初始的指针     fmt.Printf("the pointer is : %p \n", &arr)     printPointer(arr) } func printPointer(any interface{}) {

  • 深入解析golang编程中函数的用法

    函数是一组一起执行任务的语句.每Go程序具有至少一个函数,它一般是main(),以及所有的最琐碎程序可以定义附加函数. 你可以将代码放到独立的功能.如何划分代码之间的不同功能,但逻辑上的划分通常是让每个函数执行特定的任务. 函数声明告诉编译器有关的函数的名称,返回类型和参数.一个函数定义提供了函数的实际主体. Go语言标准库提供了大量的内置函数,在程序可以调用.例如,函数len()需要不同类型的参数和返回值的类型的长度.例如,如果一个字符串传递给它,它会返回字符串的长度以字节为单位,如果一个数组

  • Go语言生成随机数的方法

    本文实例讲述了Go语言生成随机数的方法.分享给大家供大家参考.具体实现方法如下: golang生成随机数可以使用math/rand包 复制代码 代码如下: package main        import (     "fmt"     "math/rand" )        func main() {     for i:=0; i<10; i++ {         fmt.Println(rand.Intn(100))     } } 发现这种情况

  • Golang编程实现生成n个从a到b不重复随机数的方法

    本文实例讲述了Golang编程实现生成n个从a到b不重复随机数的方法.分享给大家供大家参考,具体如下: 代码很简单: 复制代码 代码如下: package test import (     "fmt"     "math/rand"     "time" ) //生成若干个不重复的随机数 func RandomTestBase() {     //测试5次     for i := 0; i < 5; i++ {         nums

  • Go语言生成素数的方法

    本文实例讲述了Go语言生成素数的方法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: package main // 生成2, 3, 4, ... 到 channel 'ch'中. func Generate(ch chan<- int) {     for i := 2; ; i++ {         ch <- i  // Send 'i' to channel 'ch'.     } } // 从管道复制值 'in' 到 channel 'out', // 移除可整除的

  • Golang记录、计算函数执行耗时、运行时间的一个简单方法

    先写一个公共函数, 比如在 common 包下有这么一个方法: // 写超时警告日志 通用方法 func TimeoutWarning(tag, detailed string, start time.Time, timeLimit float64) { dis := time.Now().Sub(start).Seconds() if dis > timeLimit { log.Warning(log.CENTER_COMMON_WARNING, tag, " detailed:&quo

  • Go语言图片处理和生成缩略图的方法

    本文实例讲述了Go语言图片处理和生成缩略图的方法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: package main import (     "fmt"     "os"     "image"     "image/color"     "image/draw"     "image/jpeg" ) func main() {     f1, err := os

  • golang模板template自定义函数用法示例

    本文实例讲述了golang模板template自定义函数用法.分享给大家供大家参考,具体如下: golang的模板十分强大,其中的unix管道风格函数调用很是喜欢. 模板中有很多内置可以参看pkg文档, 另外还可以实现自定义函数. 例子如下: 复制代码 代码如下: package main import (     "text/template"     "time"     "os" ) type User struct {     Usern

  • 使用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",同时做的操作

  • python scrapy简单模拟登录的代码分析

    1.requests模块.直接携带cookies请求页面. 找到url,发送post请求存储cookie. 2.selenium(浏览器自动处理cookie). 找到相应的input标签,输入文本,点击登录. 3.scrapy直接带cookies. 找到url,发送post请求存储cookie. # -*- coding: utf-8 -*- import scrapy import re class GithubLoginSpider(scrapy.Spider): name = 'githu

  • 简单谈谈python中的语句和语法

    python程序结构 python"一切皆对象",这是接触python听到最多的总结了.在python中最基层的单位应该就是对象了,对象需要靠表达式建立处理,而表达式往往存在于语句中,多条语句组成代码块,多个代码块再组成一整个程序.python的核心其实是由语句和表达式组成.所以在这里简单探讨一下python中的语句和表达式. 因为以后可能会接触到两个版本的python,所以这里讲一讲python2与python3的语句差异: 1.python2中没有nolocal语句. 2.prin

  • 一个简单的python爬虫程序 爬取豆瓣热度Top100以内的电影信息

    概述 这是一个简单的python爬虫程序,仅用作技术学习与交流,主要是通过一个简单的实际案例来对网络爬虫有个基础的认识. 什么是网络爬虫 简单的讲,网络爬虫就是模拟人访问web站点的行为来获取有价值的数据.专业的解释:百度百科 分析爬虫需求 确定目标 爬取豆瓣热度在Top100以内的电影的一些信息,包括电影的名称.豆瓣评分.导演.编剧.主演.类型.制片国家/地区.语言.上映日期.片长.IMDb链接等信息. 分析目标 1.借助工具分析目标网页 首先,我们打开豆瓣电影·热门电影,会发现页面总共20部

  • 简单了解Python中的几种函数

    几个特殊的函数(待补充) python是支持多种范型的语言,可以进行所谓函数式编程,其突出体现在有这么几个函数: filter.map.reduce.lambda.yield lambda >>> g = lambda x,y:x+y #x+y,并返回结果 >>> g(3,4) 7 >>> (lambda x:x**2)(4) #返回4的平方 16 lambda函数的使用方法: 在lambda后面直接跟变量 变量后面是冒号 冒号后面是表达式,表达式计算

  • 简单介绍Python中的几种数据类型

    大体上把Python中的数据类型分为如下几类: Number(数字) 包括int,long,float,complex String(字符串) 例如:hello,"hello",hello List(列表) 例如:[1,2,3],[1,2,3,[1,2,3],4] Dictionary(字典) 例如:{1:"nihao",2:"hello"} Tuple(元组) 例如:(1,2,3,abc) Bool(布尔) 包括True.False 由于Pyt

  • 简单谈谈Python中的json与pickle

    这是用于序列化的两个模块: • json: 用于字符串和python数据类型间进行转换 • pickle: 用于python特有的类型和python的数据类型间进行转换 Json 模块提供了四个功能:dumps.dump.loads.load pickle 模块提供了四个功能:dumps.dump.loads.load import pickle data = {'k1':123, 'k2':888} #dumps可以将数据类型转换成只有python才认识的字符串 p_str = pickle.

  • 简单介绍Python的Tornado框架中的协程异步实现原理

    Tornado 4.0 已经发布了很长一段时间了, 新版本广泛的应用了协程(Future)特性. 我们目前已经将 Tornado 升级到最新版本, 而且也大量的使用协程特性. 很长时间没有更新博客, 今天就简单介绍下 Tornado 协程实现原理, Tornado 的协程是基于 Python 的生成器实现的, 所以首先来回顾下生成器. 生成器 Python 的生成器可以保存执行状态 并在下次调用的时候恢复, 通过在函数体内使用 yield 关键字 来创建一个生成器, 通过内置函数 next 或生

  • 简单谈谈python基本数据类型

    int(整型) 在32位机器上,整数的位数为32位,取值范围为-2**31-2**31-1,即-2147483648-2147483647 在64位系统上,整数的位数为64位,取值范围为-2**63-2**63-1,即-9223372036854775808-9223372036854775807 long(长整型) 跟C语言不同,Python的长整数没有指定位宽,即:Python没有限制长整数数值的大小,但实际上由于机器内存有限,我们使用的长整数数值不可能无限大. 注意,自从Python2.2

  • 符合语言习惯的 Python 优雅编程技巧【推荐】

    Python最大的优点之一就是语法简洁,好的代码就像伪代码一样,干净.整洁.一目了然.要写出 Pythonic(优雅的.地道的.整洁的)代码,需要多看多学大牛们写的代码,github 上有很多非常优秀的源代码值得阅读,比如:requests.flask.tornado,下面列举一些常见的Pythonic写法. 0. 程序必须先让人读懂,然后才能让计算机执行. "Programs must be written for people to read, and only incidentally f

随机推荐