PythonI/O进阶学习笔记_9.python的生成器

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

PythonI/O进阶学习笔记_9.python的生成器

besttr1225   2020-01-06 我要评论
 content:
1. 什么是生成器
2. 生成器的实现
3. 生成器的应用
 
一.生成器简介
1.什么是生成器
    在 Python 中,使用了 yield 的函数被称为生成器(generator)。
    跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象。
可以看到,普通函数就是返回的return,而生成器函数是生成了一个生成器对象。
 
为什么能和普通函数不一样返回生成器对象?
因为在python在运行之前进行编译成字节码。发现了yield关键字,所以在编译的时候就定义了。
生成器对象,实际上也是实现了我们的迭代协议的。
 
为啥会用到生成器?    
 简单举个例子:
    列表所有数据都在内存中,如果有海量数据的话将会非常耗内存。如果仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
    如果列表元素能按照某种算法推算出来,那我们就可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间。
 
生成器在python中的设计使用
实现了延迟求值和惰性求值,也是后面协程实现的基础。
 
2.生成器怎么用
例子:实现斐波拉契数列
#input
def fib(x):
    if x<3:
        return 1
    else:
        return fib(x-1)+fib(x-2)
 
def fib2(x):
    n=0
    last=1
    sum=0
    while n<x:
        yiled last 
        sum,last=last,sum+last
        n=n+1
 
if __name__=="__main__":
    f=fib(6)
    print(f)
    f2=fib2(6)
    for i in f2:
        print(i)
    pass
 
#output
8
1
1
2
3
5
8

 

二. 生成器的实现
生成器其实用起来还是比较简单的,但是不理解原理的时候,用的时候是不是虚虚的。
 
1.python函数的工作原理
python解释器实际上是用c来写的。解释器会用C实现的函数(PyEval_EvalFramEx)去执行函数。
这个 PyEval_EvalFramEx 首先会创建一个栈帧(Stack Frame)对象,就是那种记录上下文的堆栈。注意python里一切皆对象哦。
然后会将代码也变成字节码对象。查看一个函数的字节码:
#input
def foo():
    bar
def bar():
    pass
import  dis
print(dis.dis(foo))
 
#output:
  2           0 LOAD_GLOBAL              0 (bar)
              2 POP_TOP
              4 LOAD_CONST               0 (None)
              6 RETURN_VALUE
None
在调用函数之前,会创建那个栈帧对象,然后在上下文中,运行这个全局唯一的字节码。
当foo调用bar的时候,又会创建一个栈帧,然后将bar的控制权交给foo的栈帧对象。
所有栈帧都是分配在堆内存(不去释放,就一直在内存中)上,这就决定了栈帧可以独立于调用者存在。
 
什么意思呢?
就是在foo函数退出之后,我们仍然可以找到之前调用过的foo,或者它的子函数bar的栈帧,并没有和静态语言一样函数运行完了之后就被释放。
 
2.生成器对象原理
假设我们实现一个生成器函数:
def gen_func():
    yield 1
    name="bobby"
    yield 2
    age=30
    return "tangrong"
这个生成器对象实际上如下图所示:
实际上,就是在1中的PyFrameObject上面,再封了一层,为PyGenObject。
并且,再yield时候,实际上就是暂停了最近的那句代码。当时的上下文都是被保存的,即f_lasti,f_locals。在任何地方都可以暂停和控制它。
查看yield时,保存的lasti和locals:
#input
def gen_func():
    yield 1
    name="bobby"
    yield 2
    age=30
    return "tangrong"
 
gen=gen_func()
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
next(gen)
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
next(gen)
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
 
#outpu:
-1
{}
2
{}
12
{'name': 'bobby'}

 

 
三.生成器的应用
1.生成器在Userlist中的应用
    我们知道,对list可以进行循环遍历。因为其是可迭代的。如果实现了__getitem__也是可以进行for遍历的。而且是会先去查找__iter__,没有发现__iter__魔法方法才会去找__getitem__方法。
    我们去看list类的源码的时候,其实就是提供了给我们看的接口,实际的c语言实现并看不到。而且我们在定制自己的List类的时候,是完全不提倡去继承list的,因为里面的很多关键方法是不能被重写的。但是python提供了UserList,即python实现的list。
首先,UserList是继承的MutableSequence。
而在MutableSequence的__iter__的实现中,就应用到了生成器。
 
 
2.生成器是如何读取大文件的(如何使用生成器表达式)
#将一个500g的文件读取出来 写入数据库 并且这个文件只有一行数据,有特定的分隔符。
#如果是多行的话,用open一行行读取还是可以的。
#但是实际上!! 文件对象的read函数,是可以传递我要读取的大小的,并且偏移量会被记录。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们