一、迭代器
顾名思义,迭代器就是用于迭代操作(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: None
第0次循环: 结束
第1次循环: 开始
1
----------------------------------------
res: None
第1次循环: 结束
第2次循环: 开始
2
----------------------------------------
res: None
第2次循环: 结束
第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 想象成return ,return 了一个0 之后,程序停止,并没有执行赋值给res 操作,此时next(g) 语句执行完成;打印结果: 第0次循环: 开始
0
-
程序执行print("----------------------------------------") ,打印结果: ----------------------------------------
-
开始执行接下来的print(next(g)) -
这个时候和上面那个差不多,不过不同的是,这个时候是从上一个next()程序停止的地方开始执行的(即:继续从上一次yield代码处向下执行代码),也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,程序继续在for执行,直到又一次碰到yield,打印结果: res: None
第0次循环: 结束
第1次循环: 开始
1
-
程序执行print("----------------------------------------") ,打印结果: ----------------------------------------
-
从上一个next()程序停止的地方开始执行的,即:继续从上一次yield代码处向下执行代码,直到又一次碰到yield,打印结果: res: None
第1次循环: 结束
第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: None
第0次循环: 结束
第1次循环: 开始
1
----------------------------------------
res: None
第1次循环: 结束
第2次循环: 开始
2
----------------------------------------
res: 7
第2次循环: 结束
第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的用法详解——最简单,最清晰的解释
|