Python 生成器

fansichao 2021-10-23 16:16:35
Categories: > Tags:

列表/字典推导式

列表推导式 样例

1
2
3
4
5
6
7
8
9
10
11
12
13
# >> 实现列表自加一

info = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 方法1 enumerate循环
for index,i in enumerate(info):
info[index] +=1
# 方法2 map方法
a = map(lambda x:x+1,info)
# 方法3 列表推导式
a = [i+1 for i in range(10)]

In [3]: print a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

字典推导式 样例

1
2
In [5]: {k:k+1 for k in info}
Out[5]: {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}

生成器

生成器表达式

把一个列表生成式的[]中括号改为()小括号,就创建一个 generator

1
2
3
4
5
6
7
8
9
10
#列表生成式
lis = [x*x for x in range(10)]
print(lis)
#生成器
generator_ex = (x*x for x in range(10))
print(generator_ex)

结果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x000002A4CBF9EBA0>

生成器表达式使用
使用next()获取生成器的下一个返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
generator_ex = (x*x for x in range(4))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
结果:
0
1
4
9
# 超出值时会抛出异常
Traceback (most recent call last):
File "列表生成式.py", line 42, in <module>
print(next(generator_ex))
StopIteration

创建生成器后,一般使用for循环实现。

生成器函数

使用yield 构造生成器函数
yield 是一个类似 return 的关键字,迭代一次遇到 yield 的时候就返回 yield 后面或者右面的值。
而且下一次迭代的时候,从上一次迭代遇到的 yield 后面的代码开始执行

任何使用了 yield 的函数就是生成器,生成器就是一个返回迭代器的函数,或者说生成器就是一个迭代器。

1
2
3
4
5
6
7
8
9
10
11
def fib(max):
n,a,b =0,0,1
while n < max:
yield b
a,b =b,a+b
n = n+1

a = fib(10)
print next(a)
for i in a:
print i

小结

生成器的用法

生成器用法样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#! -*- coding:utf-8 -*-
def iterator_func(val):
for i in range(val):
print("生成器值:%s"%i)
tmp = yield i
if tmp:
print("send传递值:%s"%tmp)
num = 5
gen = iterator_func(num)
# 必须先next()调用,开始生成器
gen.next()
for i in range(10):
print(">>>>>>>> ")
gen.send(i + 10)
if i >= num:
# throw 方法自定义异常
gen.throw(Exception,u"自定义异常:数值不够")
if i >= 1:
# close()后. 到下一个next或send直接抛出异常
gen.close()

生成器值:0
>>>>>>>>
send传递值:10
生成器值:1
>>>>>>>>
send传递值:11
生成器值:2
>>>>>>>>
Traceback (most recent call last):
File "a", line 14, in <module>
gen.send(i+10)
StopIteration

生成器的分类

生成器的优点

模拟并发。Python 虽然支持多线程,但是由于 GIL(全局解释锁,Global Interpreter Lock)的存在,同一个时间,只能有一个线程在运行,所以无法实现真正的并发。这时就出现了协程。复杂解释不说了,简单说协程就是你可以暂停执行的函数”。也就是 yield。

Python 实现协程最简单的方法,就是使用 yield。当一个函数在执行过程中被阻塞时,就用 yield 挂起,然后执行另一个函数。当阻塞结束后,可以用 next()或者 send()唤醒。相比多线程,协程的好处是它在一个线程内执行,避免线程之间切换带来的额外开销,而且协程不存在加锁的步骤。

迭代器

迭代器包含有 next 方法的实现,在正确的范围内返回期待的数据以及超出范围后能够抛出 StopIteration 的错误停止迭代。

Iterable 可迭代对象

使用 isinstance()判断一个对象是否为可 Iterable 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Iterable 可迭代对象
In [1]: from collections import Iterable

In [2]: isinstance([], Iterable)
Out[2]: True

In [3]: isinstance((x for x in range(10)), Iterable)
Out[3]: True

In [4]: isinstance('test', Iterable)
Out[4]: True

In [5]: isinstance(123, Iterable)
Out[5]: False

Iterator 迭代器

一个实现了 iter 方法的对象是可迭代的,一个实现 next 方法并且是可迭代的对象是迭代器。

可以被 next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

所以一个实现了 iter 方法和 next 方法的对象就是迭代器。

1
2
3
4
5
6
7
In [6]: from collections import Iterator

In [7]: isinstance((x for x in range(10)), Iterator)
Out[7]: True

In [8]: isinstance([], Iterator)
Out[8]: False

迭代器和可迭代对象之间的转换

Iterable 转为 Iterator: iter([1,2,3])
Iterator 转为 Iterable: list((x+1 for x in range(10)))

1
2
3
4
5
6
7
8
9
10
11
In [11]: isinstance(iter([]), Iterator)
Out[11]: True

In [12]: isinstance(iter('abc'), Iterator)
Out[12]: True

In [13]: isinstance([], Iterator)
Out[13]: False

In [15]: isinstance(list((x for x in range(10))),Iterator)
Out[15]: False

总结

对 yield 的总结
  (1)通常的 for..in…循环中,in 后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。他可以是 a = [1,2,3],也可以是 a = [x*x for x in range(3)]。

它的缺点也很明显,就是所有数据都在内存里面,如果有海量的数据,将会非常耗内存。

(2)生成器是可以迭代的,但是只可以读取它一次。因为用的时候才生成,比如 a = (x*x for x in range(3))。!!!!注意这里是小括号而不是方括号。

(3)生成器(generator)能够迭代的关键是他有 next()方法,工作原理就是通过重复调用 next()方法,直到捕获一个异常。

(4)带有 yield 的函数不再是一个普通的函数,而是一个生成器 generator,可用于迭代

(5)yield 是一个类似 return 的关键字,迭代一次遇到 yield 的时候就返回 yield 后面或者右面的值。而且下一次迭代的时候,从上一次迭代遇到的 yield 后面的代码开始执行

(6)yield 就是 return 返回的一个值,并且记住这个返回的位置。下一次迭代就从这个位置开始。

(7)带有 yield 的函数不仅仅是只用于 for 循环,而且可用于某个函数的参数,只要这个函数的参数也允许迭代参数。

(8)send()和 next()的区别就在于 send 可传递参数给 yield 表达式,这时候传递的参数就会作为 yield 表达式的值,而 yield 的参数是返回给调用者的值,也就是说 send 可以强行修改上一个 yield 表达式值。

(9)send()和 next()都有返回值,他们的返回值是当前迭代遇到的 yield 的时候,yield 后面表达式的值,其实就是当前迭代 yield 后面的参数。

(10)第一次调用时候必须先 next()或 send(),否则会报错,send 后之所以为 None 是因为这时候没有上一个 yield,所以也可以认为 next()等同于 send(None)

yield 实现单线程并发

yield 单线程并发样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#! -*- coding:utf-8 -*-
import time

def consumer(name):
print('%s 准备学习了~' %(name))
while True:
lesson = yield
print('开始[%s]了,[%s]老师来讲课了~' %(lesson,name))

def producer(name):
c1 = consumer('A')
c2 = consumer('B')
c1.next() # 先调用c1使后面的send能够传值
c2.next() # 先调用c2使后面的send能够传值
print('同学们开始上课了~')
for i in range(3):
time.sleep(1)
print('到了两个同学')
c1.send(i)
c2.send(i)

producer('westos')

# > 返回结果
A 准备学习了~
B 准备学习了~
同学们开始上课了~
到了两个同学
开始[0]了,[A]老师来讲课了~
开始[0]了,[B]老师来讲课了~
到了两个同学
开始[1]了,[A]老师来讲课了~
开始[1]了,[B]老师来讲课了~
到了两个同学
开始[2]了,[A]老师来讲课了~
开始[2]了,[B]老师来讲课了~

"""
利用了关键字yield一次性返回一个结果,阻塞,重新开始
send 唤醒
"""

第三方函数库-greenlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
"""
使用greenlet完成多任务
为了更好的使用协程来完成多任务,python中的greeblet模块
对其进行的封装
"""
from greenlet import greenlet
import time

def test1():
while True:
print('---A----')
gr2.switch()
time.sleep(0.5)

def test2():
while True:
print('----B----')
gr1.switch()
time.sleep(0.5)

"""
greenlet这个类对yield进行的封装
"""
gr1= greenlet(test1)
gr2 = greenlet(test2)
# 相当于开关,开启后两个函数之间能够相互切换执行
gr1.switch()

参考资源

生成器使用
python(生成式、生成器)