Python 生成器

生成器的本质和定义方式

本质:生成器实际上就是一个迭代器。

生成器的定义方式主要有三种:

  • 通过生成器函数
  • 通过各种推导式来实现生成器
  • 通过数据的转换也可以获取生成器

生成器函数

1
2
3
4
5
6
7
8
9
10
# 普通函数
def func():
print("My name is cdc")
return "Hello"

print(func())
"""
My name is cdc
Hello
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 函数中包含了yield, 当前这个函数就不再是普通的函数了. 是生成器函数
def func():
print("My name is cdc")
yield "Hello"

func() # 什么都没有打印
print(func()) # <generator object func at 0x000001F69ADAC258> 输出结果是一个生成器函数对象的地址,因此func()这一步相当于就是创建了一个生成器

# 生成器本质就是装饰器,因此可以用next方法取值
g = func()
print(g.__next__())
"""
结果
My name is cdc
Hello
"""
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 对于普通函数而言,出现多个return,只会执行到第一return,后面的语句不再执行
def func():
print("My name is cdc")
return "Hello"
print("My age is 18")
return "18"
print("哈哈哈")

func()
"""
结果
My name is cdc
Hello
"""


# 对于生成器而言,有多个yield并不影响函数的执行,每当调用一次next,就会从上一个yield执行到下一个yield
def func():
print("My name is cdc")
yield "Hello"
print("My age is 18")
yield "18"
print("哈哈哈")

g = func()
print(g.__next__())
print(g.__next__())
"""
结果
My name is cdc
Hello
My age is 18
18
"""

# 若通过next取值的个数超过函数中yield的个数,则溢出,报停止迭代错误
def func():
print("My name is cdc")
yield "Hello"
print("My age is 18")
yield "18"
print("哈哈哈")

g = func()
print(g.__next__())
print(g.__next__())
print(g.__next__())
"""
My name is cdc
Hello
My age is 18
18
哈哈哈
Traceback (most recent call last):
File "F:/生成器.py", line 12, in <module>
print(g.__next__())
StopIteration
"""

生成器函数的send方法

send 方法也和 next 一样,也可以让生成器向下执行一次,但是同时可以给上一个 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
def func():
print("My name is cdc")
a = yield "Hello"
print("a",a)
print("My age is 18")
b = yield "18"
print("哈哈哈")
c = yield "over"


g = func()
print(g.__next__())
print(g.send(1111))
print(g.__next__())

"""
My name is cdc
Hello
a 1111
My age is 18
18
哈哈哈
over
"""

send 方法的注意事项:

  • send 方法前面必须有next方法,即send方法不能作为第一个方法使用。因为 send 方法是将值传到前一个 yield 中,若一开始就使用 send 方法,将找不到 yield;
  • send 方法不能放在最后使用,因为 send 和 next 一样,调用时会从当前的 yield 执行到下一个 yield,最后使用 send,找不到下一个 yield 将会报溢出错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def func():
print("My name is cdc")
a = yield "Hello"
print("a",a)
print("My age is 18")
b = yield "18"
print("b",b)
print("哈哈哈")
c = yield "over"
print("c",c)
print("呵呵")

# 不能将send作为第一个方法
g = func()
print(g.send(2222)) # TypeError: can't send non-None value to a just-started generator
print(g.send(1111))
print(g.__next__())
print(g.__next__())

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
def func():
print("My name is cdc")
a = yield "Hello"
print("a", a)
print("My age is 18")
b = yield "18"
print("b", b)
print("哈哈哈")
c = yield "over" # 此处c还是可以接收到最后一个send传过来的值的,只是再往下找yield的时候会报错
print("c", c)
print("呵呵")

# 不能在最后使用send
g = func()
print(g.__next__())
print(g.send(1111))
print(g.__next__())
print(g.send(2222))

"""
My name is cdc
Hello
a 1111
My age is 18
18
b None
哈哈哈
over
c 2222
呵呵
Traceback (most recent call last):
print(g.send(2222))
StopIteration
"""

生成器的元素可以通过 for 循环遍历取出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def func():
yield 111
yield 222
yield 333
yield 444

g = func()
for i in g:
print(i)

"""
111
222
333
444
"""

生成器的元素还可以通过转换数据类型获取:

1
2
3
4
5
6
7
8
9
10
11
def func():
yield 111
yield 222
yield 333
yield 444

g = func()
lst = list(g)
print(lst)

# [111, 222, 333, 444]

转换成列表类型后,会自动执行生成器中的 next 方法取出所有数据

推导式实现生成器

一、列表推导式

首先我们来看一段代码,要求给出一个列表,向列表中添加1-13

1
2
3
lst = list()
for i in range(1,14):
lst.append(i)

换成列表推导式的形式

语法 :[最终结果(变量) for 变量 in 可迭代对象]

返回结果:按要求操作过后的列表

1
lst = [i for i in range(1, 14)]

列表推导式的常用语法: [ 结果 for 变量 in 可迭代对象]

1
2
3
# 例:从python1到python14写入列表lst
lst = ['python%s' % i for i in range(1,15)]
print(lst)

还可以对列表中的数据进行筛选:[ 结果 for 变量 in 可迭代对象 if 条件 ]

1
2
3
# 获取1-100内所有的偶数 
lst = [i for i in range(1, 100) if i % 2 == 0]
print(lst)
1
2
3
4
5
6
7
8
9
10
11
# 小练习
# 1. 获取1-100内能被3整除的数
lst = [i for i in range(1, 101) if i % 3 == 0]

# 2. 100以内能被3整除的数的平方
lst = [i * i for i in range(1, 101) if i % 3 == 0]

# 3. 寻找名字中带有两个e的⼈的名字
names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],
['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']]
lst = [name for first in names for name in first if name.count("e") == 2]

二、生成器表达式

生成器表达式和列表推导式的语法基本上是一样的,只是把[]替换成()

1
2
3
gen = (i for i in range(10)) 
print(gen)
# 打印结果是一个生成器: <generator object <genexpr> at 0x106768f10>

可以通过for循环取值

1
2
3
gen = (i for i in range(10)) 
for i in gen:
print(i)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 小练习 生成器表达式版
# 获取1-100内能被3整除的数
gen = (i for i in range(1,100) if i % 3 == 0)
for num in gen:
print(num)

# 100以内能被3整除的数的平方
gen = (i * i for i in range(100) if i % 3 == 0)
for num in gen:
print(num)

# 寻找名字中带有两个e的⼈人的名字
gen = (name for first in names for name in first if name.count("e") >= 2)
for name in gen:
print(name)

生成器表达式和列表推导式的区别:

  • 列表推导式比较耗内存,将所有数据一次性加载。生成器表达式几乎不占用内存,使用的时候才分配和使用内存
  • 得到的值不一样,列表推导式得到的是一个列表,生成器表达式获取的是一个生成器。

使用生成器表达式的好处就在于节省内存空间,比如一共有一万个元素,使用列表就需要为这一万个元素开辟一万个内存空间。使用生成器只需要开辟一个内存空间,由于生成器的惰性机制,只有问它要值得时候才会给你返回,因此当第一个值使用完后,就会把内存空间释放,放索取第二个值的时候,再开辟一个内存空间给第二个值使用,使用后再释放。所有对于一万个元素来说,只用了一个元素的内存空间。

其他推导式

一、字典推导式

1
2
3
4
5
6
7
8
9
10
11
# 把字典中的key和value互换 
dic = {'a': 1, 'b': '2'}
new_dic = {dic[key]: key for key in dic}
print(new_dic)

# 在以下list中. 从lst1中获取的数据和lst2中相对应的位置的数据组成⼀个新字典
lst1 = ['jay', 'jj', 'sylar']
lst2 = ['周杰伦', '林林俊杰', '邱彦涛']
dic = {lst1[i]: lst2[i] for i in range(len(lst1))}
print(dic)

二、集合推导式

1
2
3
4
# 绝对值去重 
lst = [1, -1, 8, -8, 12]
s = {abs(i) for i in lst}
print(s)

注:没有元组推导式


Python 生成器
https://clark-cdc.github.io/2019/05/01/0009-生成器/
作者
clark
发布于
2019年5月1日
许可协议