首页 » 编程 » Python基础 » 正文

生成器,协成函数

day23


生成器就是一个函数,这个函数内包含有yield这个关键字。


生成器与return有何区别?

return 只能返回一次函数就彻底结束了,而yieId能返回多次。


yield到底干了什么事情:

yield把函数变成迭代器。


总结yield的功能:

  1. 相当于把__next__ 和 __next__ 方法封装到函数内部

  2. 与return相比,return只能返回一次,而yield能返回多次

  3. 函数暂停已经继续运行的状态是通过yield保存的


from collections import Iterator

# 生成器就是一个函数,这个函数内包含有yield这个关键字
def test():
    print("first")
    yield 1  # 相当于 return 1
    
test()  # 调用没有任何输出
g = test()  # 返回了一个内存地址,是生成器对象
print(g)

print(isinstance(g, Iterator))  # 返回True ,说明是迭代器
# next(g)   # 得到输出 first
print(next(g))  # 同时得到了 输出与返回值  first 和 1 ,如果之前已经next过了 则会报错

输出结果:

<generator object test at 0x000001D79C23A678>
True
first
1


例子:

def test():
    print("hello1")
    yield 1
    print("hello2")
    print("hello222")
    yield 2


g = test()
print(g.__next__())  # 会输出第一个 yield之前的打印信息
print(g.__next__())  # 会输出第二个和第一个 yield之间的打印信息
print(g.__next__())  # 再执行的时候就会报StopIteration异常了,因为只有2次迭代,已经没有值了

# 既然是迭代器,说明可以进行循环
# while循环 ,执行时请将之前的执行代码注释
while True:
    try:
        print(g.__next__())
    except StopIteration:
        break

# for循环,执行时请将之前的执行代码注释
for i in g:
    print(i)

输出结果都是:

hello1
1
hello2
hello222
2



生成器应用:

在shell中我们有tail命令可以实时查看某文件增加的内容,以及可以通过grep只查看含有指定关键字的内容,我们可以使用python来写一个,运用到生成器知识。

import time


def tail(file_path):
    with open(file_path, "r") as f:
        f.seek(0, 2)  # 光标从后开始数
        while True:
            line = f.readline()
            if not line:
                time.sleep(0.5)
                continue
            else:
                yield line


def grep(keyword, lines):
    for line in lines:
        if keyword in line:
            print(line)


g = tail("a.txt")
grep("error", g)

建议在linux环境下看效果,运行后使用echo往a.txt文件中新增包含error或不包含error的内容即可看效果。



协成函数:

例子:

# 吃包子例子

def eat(name):
    print("%s is start to eat baozi." % name)
    while True:
        food = yield
        print("%s is get %s, start eat it." % (name, food))
    print("done")


g = eat("alex")  # 将函数转为生成器
g.__next__()  # 触发函数运行,这里是第一次运行 碰到yield暂停,只输出了yield前面的内容
g.send("白菜包子")  # send功能和next一样,但是可以将值传给yield  ,这里将“白菜包子”传给yield ,
                    # 这里是第二次运行从上一次yield停止的地方开始运行,将白菜包子传给food后 继续运行之后的程序
g.send("韭菜包子")  # 这里是第三次运行,由于一直在循环中,所以将 韭菜包子又传给yield运行

输出结果:

alex is start to eat baozi.
alex is get 白菜包子, start eat it.
alex is get 韭菜包子, start eat it.


将吃过的包子保存下来:

# 吃包子例子

def eat(name):
    print("%s is start to eat baozi." % name)
    food_list = []  # 设置列表用来存放吃过的
    while True:
        food = yield food_list  # 返回list列表
        print("%s is get %s, start eat it." % (name, food))
        food_list.append(food)  # 将已经吃了的加入列表
    print("done")


g = eat("alex")
g.__next__()
print(g.send("白菜包子"))  # 打印出返回结果,也就是列表内容
print(g.send("韭菜包子"))  # 打印出返回结果,也就是列表内容

运行结果:

alex is start to eat baozi.
alex is get 白菜包子, start eat it.
['白菜包子']
alex is get 韭菜包子, start eat it.
['白菜包子', '韭菜包子']


e.send 与 next(e) 的区别:

  1. 如果函数内的yield是表达式形式,那么必须先next(e)

  2. 二者的共同之处是都可以让函数在上次暂停的位置继续运行,

  3. 不同的地方在于send在触发下一次代码的执行时,会顺便给yield传一个值



如果函数内的yield是表达式形式使用send时需要先执行next(e),有时会忘记,所以写一个装饰器来处理,但其他函数需要用时也可调用:

# 吃包子例子

def init(func):
    "这个装饰器用于执行第一next"

    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        next(res)
        return res

    return wrapper


@init
def eat(name):
    print("%s is start to eat baozi." % name)
    food_list = []  # 设置列表用来存放吃过的
    while True:
        food = yield food_list  # 返回list列表
        print("%s is get %s, start eat it." % (name, food))
        food_list.append(food)  # 将已经吃了的加入列表
    print("done")


g = eat("alex")
# g.__next__()   # 有了初始化装饰器就可以不用next了
print(g.send("白菜包子"))  # 打印出返回结果,也就是列表内容
print(g.send("韭菜包子"))  # 打印出返回结果,也就是列表内容

运行结果:

alex is start to eat baozi.
alex is get 白菜包子, start eat it.
['白菜包子']
alex is get 韭菜包子, start eat it.
['白菜包子', '韭菜包子']


发表评论

验证码加载中....