IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> Python中迭代器、生成器的用法:【使用了yield的函数称为生成器】【生成器是一个返回迭代器的函数】【用List遍历数据会一次性加载所有数据,占用内存太大;生成器可以分批次向内存加载数据】 -> 正文阅读

[Python知识库]Python中迭代器、生成器的用法:【使用了yield的函数称为生成器】【生成器是一个返回迭代器的函数】【用List遍历数据会一次性加载所有数据,占用内存太大;生成器可以分批次向内存加载数据】

一、迭代器

顾名思义,迭代器就是用于迭代操作(for 循环)的对象,它像列表一样可以迭代获取其中的每一个元素。

python3中任何实现了 _next_ 方法的对象都可以称为迭代器。

迭代器与列表的区别在于,构建迭代器的时候,不像列表把所有元素一次性加载到内存,而是以一种延迟计算(lazy evaluation)方式返回元素,这正是它的优点。

比如列表含有中一千万个整数,需要占超过400M的内存,而迭代器只需要几十个字节的空间。因为它并没有把所有元素装载到内存中,而是等到调用 next 方法时候才返回该元素(按需调用 call by need 的方式,本质上 for 循环就是不断地调用迭代器的next方法)。

以斐波那契数列为例来实现一个迭代器:

class Fib:
    def __init__(self, n):
        self.prev = 0
        self.cur = 1
        self.n = n

    def __next__(self):
        if self.n > 0:
            value = self.cur
            self.cur = self.cur + self.prev
            self.prev = value
            self.n -= 1
            return value
        else:
            raise StopIteration()


f = Fib(5)
print(type(f))
print(f.__next__())
print(f.__next__())
print(next(f))
print(f.__next__())
print(next(f))
print(f.__next__())

打印结果:

<class '__main__.Fib'>
1
1
2
3
5
Traceback (most recent call last):
  File "test03.py", line 25, in <module>
    print(f.__next__())
  File "test03.py", line 15, in __next__
    raise StopIteration()
StopIteration

Process finished with exit code 1

二、生成器

在 Python 中,使用了 yield 的函数被称为生成器(generator)。

跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

调用一个生成器函数,返回的是一个迭代器对象

为什么用这个生成器,是因为如果用List的话,会占用更大的空间,比如说取0,1,2,3,4,5,6............100000000

在python2中,你可能会这样:

for i in range(100000000):
    a=i

这个时候range(100000000)就默认生成一个含有100000000个数的list了,所以很占内存。

这个时候你可以用刚才的yield组合成生成器进行实现:

  • 第一种写法:

    def foo(num):
        print("starting...")
        while num < 10:
            num = num + 1
            yield num
    
    
    print(type(foo))
    
    f = foo(0)
    print(f)
    print('------------------------------')
    print(f.__next__())
    print(f.__next__())
    print(f.__next__())
    

    打印结果:

    <class 'function'>
    <generator object foo at 0x000001B9F5130750>
    ------------------------------
    starting...
    1
    2
    3
    
    Process finished with exit code 0
    
  • 第二种写法:

    def foo(num):
        print("starting...")
        while num < 10:
            num = num + 1
            yield num
    for n in foo(0):
        print(n)
    

    打印结果:

    starting...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    

在python2中也可以用xrange(1000)这个生成器实现 yield 组合:xrange(100000000)

for n in xrange(100000000):
    a=n

其中要注意的是python3时已经没有xrange()了,在python3中,range()就是xrange()了,你可以在python3中查看range()的类型,它已经是个<class ‘range’>了,而不是一个list了,毕竟这个是需要优化的。

二、生成器用法

1、yield

首先,如果你还没有对yield有个初步分认识,那么你先把yield看做“return”,这个是直观的,它首先是个return,普通的return是什么意思,就是在程序中返回某个值,返回之后程序就不再往下运行了。看做return之后再把它看做一个是生成器(generator)的一部分(带yield的函数才是真正的迭代器),好了,如果你对这些不明白的话,那先把yield看做return,然后直接看下面的程序,你就会明白yield的全部意思了:

def foo():
    print("------------------- 进入foo()函数 -------------------")
    for i in range(10):
        print('第{0}次循环: 开始'.format(i))
        res = yield i
        print("res:", res)
        print('第{0}次循环: 结束'.format(i))


g = foo()
print('g = ', g)
print('********************* 开始执行 *********************')
print(next(g))
print("----------------------------------------")
print(next(g))
print("----------------------------------------")
print(next(g))
print("----------------------------------------")
print(next(g))

打印结果:

g =  <generator object foo at 0x000001CB3B2677C8>
********************* 开始执行 *********************
------------------- 进入foo()函数 -------------------0次循环: 开始
0
----------------------------------------
res: None0次循环: 结束
第1次循环: 开始
1
----------------------------------------
res: None1次循环: 结束
第2次循环: 开始
2
----------------------------------------
res: None2次循环: 结束
第3次循环: 开始
3

Process finished with exit code 0

代码运行顺序如下:

  • 程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器generator(相当于一个对象)

  • 直到调用next方法,foo函数正式开始执行,先执行foo函数中的print("------------------- 进入foo()函数 -------------------")方法,打印结果:

    ------------------- 进入foo()函数 -------------------
    
  • 进入for循环

  • 执行for循环里的代码print('第{0}次循环: 开始'.format(i)),然后遇到yield关键字,然后yield想象成returnreturn了一个0之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成;打印结果:

    0次循环: 开始
    0
    
  • 程序执行print("----------------------------------------"),打印结果:

    ----------------------------------------
    
  • 开始执行接下来的print(next(g))

  • 这个时候和上面那个差不多,不过不同的是,这个时候是从上一个next()程序停止的地方开始执行的(即:继续从上一次yield代码处向下执行代码),也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,程序继续在for执行,直到又一次碰到yield,打印结果:

    res: None0次循环: 结束
    第1次循环: 开始
    1
    
  • 程序执行print("----------------------------------------"),打印结果:

    ----------------------------------------
    
  • 从上一个next()程序停止的地方开始执行的,即:继续从上一次yield代码处向下执行代码,直到又一次碰到yield,打印结果:

    res: None1次循环: 结束
    第2次循环: 开始
    2
    
  • 直至for循环结束;

到这里你可能就明白yield和return的关系和区别了,带yield的函数是一个生成器,而不是一个函数了,这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数,这一次的next开始的地方是接着上一次的next停止的地方执行的,所以调用next的时候,生成器并不会从foo函数的开始执行,只是接着上一步停止的地方开始,然后遇到yield后,return出要生成的数,此步就结束。

2、send

再看一个这个生成器的send函数的例子,这个例子就把上面那个例子的最后一行换掉了

def foo():
    print("------------------- 进入foo()函数 -------------------")
    for i in range(10):
        print('第{0}次循环: 开始'.format(i))
        res = yield i
        print("res:", res)
        print('第{0}次循环: 结束'.format(i))


g = foo()
print('g = ', g)
print('********************* 开始执行 *********************')
print(next(g))
print("----------------------------------------")
print(next(g))
print("----------------------------------------")
print(next(g))
print("----------------------------------------")
print(g.send(7))

输出结果:

g =  <generator object foo at 0x00000227A3B667C8>
********************* 开始执行 *********************
------------------- 进入foo()函数 -------------------0次循环: 开始
0
----------------------------------------
res: None0次循环: 结束
第1次循环: 开始
1
----------------------------------------
res: None1次循环: 结束
第2次循环: 开始
2
----------------------------------------
res: 72次循环: 结束
第3次循环: 开始
3

Process finished with exit code 0

send函数的概念:send是发送一个参数给res的,因为上面讲到,return的时候,并没有把 i i i 赋值给 r e s res res,下次执行的时候只好继续执行赋值操作,只好赋值为None了,而如果用 send 的话,开始执行的时候,先接着上一次 yield i 执行,先把 7 7 7 赋值给了 r e s res res,然后执行 next 的作用,遇见下一回的 yield,return 出结果后结束。

  • 程序执行g.send(7),程序会从yield关键字那一行继续向下运行,send会把7这个值赋值给res变量
  • 由于send方法中包含next()方法,所以程序会继续向下运行执行print方法,然后再次进入for循环
  • 程序执行再次遇到yield关键字,yield会返回后面的值后,程序再次暂停,直到再次调用next方法或send方法。



参考资料:
python中yield的用法详解——最简单,最清晰的解释

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2021-09-27 14:02:00  更:2021-09-27 14:02:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 15:51:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码