@Author : Roger TX (425144880@qq.com)
@Link : https://github.com/paotong999
一、Python生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。
1、创建一个包含100万个元素的列表,需要占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
2、如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?
3、这样就不必创建完整的list,从而节省大量的空间。
在Python中,这种一边循环一边计算的机制,称为生成器:generator
。
Python 主要提供了以下两种方式来创建生成器:
- 使用
for
循环的生成器推导式。 - 调用带
yield
语句的生成器函数。
1、for 循环的生成器推导式
只用把一个列表生成式的[]改成(),就可以生成一个 generator
。
>>> L = [x * x for x in range(5)]
>>> L
[0, 1, 4, 9, 16]
>>> g = (x * x for x in range(5))
>>> g
<generator object <genexpr> at 0x1022ef630>
L是一个list,而g是一个generator
可以通过next()函数获得generator的下一个返回值:
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
当然我们还可以使用for循环,因为generator也是可迭代对象:
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
9
16
2、带 yield
语句的生成器函数
- 定义一个包含
yield
语句的函数。 - 调用第 1 步创建的函数得到生成器。
下面程序使用生成器来定义一个差值递增的数列。程序先定义了一个包含 yield
语句的函数:
def test(val, step):
print("--------函数开始执行------")
cur = 0
# 遍历0~val
for i in range(val):
# cur添加i*step
cur += i * step
yield cur
上面函数与前面介绍的普通函数的最大区别在于 yield cur
这行,如果将这行代码改为 print(cur)
,那么这个函数就显得比较普通了,该函数只是简单地遍历区间,并将循环计数器乘以 step
后添加到 cur
变量上,该数列中两个值之间的差值会逐步递增。yield cur 语句的作用有两点:
- 每次返回一个值,有点类似于
return
语句。 - 冻结执行,程序每次执行到
yield
语句时就会被暂停。
1、在程序被 yield 语句冻结之后,当程序再次调用 next() 函数获取生成器的下一个值时,程序才会继续向下执行。
2、需要指出的是,调用包含 yield 语句的函数并不会立即执行,它只是返回一个生成器。
3、只有当程序通过 next() 函数调用生成器或遍历生成器时,函数才会真正执行。
杨辉三角定义如下:
1
/ \
1 1
/ \ / \
1 2 1
/ \ / \ / \
1 3 3 1
/ \ / \ / \ / \
1 4 6 4 1
把每一行看做一个list,试写一个generator,不断输出下一行的list:
def triangles():
L = [[1],[1,1]]
print(id(L[0]))
yield L[0]
print(id(L[0]),' ',id(L[1]))
yield L[1]
while True:
L.append([1])
for ele in range(len(L[-2]) - 1):
L[-1].append(L[-2][ele] + L[-2][ele + 1])
L[-1].append(1)
for x in range(len(L)):
print(id(L[x]),'a', end = ' ')
print ()
yield L[-1]
def triangles_tx():
L = [1]
yield L
while True:
num = len(L)
for ele in range(num - 1):
L.append(L[ele]+L[ele + 1])
L.append(1)
for ek in range(num-1):
L.remove(L[0])
yield L[-num-1:]
def triangles_tuidao():
p = [1]
while True:
for x in range(len(p)):
print(id(p[x]),'a', end = ' ')
print ()
yield p
p =[1]+[p[x]+p[x+1] for x in range(len(p)-1)]+[1]
n = 0
results = []
for t in triangles_tx():
print(t)
results.append(t)
n = n + 1
if n == 10:
break
1、triangles() 和 triangles_tuidao() 函数都可以测试通过
2、triangles_tx() 函数不能测试通过,因为列表的第一值不对
3、第一个值错误的原因是,yield 定义的生成器是一个列表,是可变元素
4、生成器元素尽量设置为不可变元素,具体原因参考 07--Python 函数参数传递机制
3、使用生成器至少有以下几个优势:
- 当使用生成器来生成多个数据时,程序是按需获取数据的,它不会一开始就把所有数据都生成出来,而是每次调用 next() 获取下一个数据时,生成器才会执行一次,因此可以减少代码的执行次数。比如前面介绍的示例,程序不会一开始就把生成器函数中的循环都执行完成,而是每次调用 next() 时才执行一次循环体。
- 当函数需要返回多个数据时,如果不使用生成器,程序就需要使用列表或元组来收集函数返回的多个值,当函数要返回的数据量较大时,这些列表、元组会带来一定的内存开销。如果使用生成器就不存在这个问题,生成器可以按需、逐个返回数据。
- 使用生成器的代码更加简洁。
二、Python迭代器
1、迭代器
我们已经知道,可以直接作用于for
循环的数据类型有以下几种:
- 一类是集合数据类型,如
list
、tuple
、dict
、set
、str
等 - 一类是
generator
,包括生成器和带yield
的generator function
这些可以直接作用于 for
循环的对象统称为可迭代对象: Iterable
。
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False
生成器不但可以作用于 for
循环,还可以被 next()
函数不断调用并返回下一个值,直到最后抛出 StopIteration
错误表示无法继续返回下一个值了。
可以被 next()
函数调用并不断返回下一个值的对象称为迭代器: Iterator
。
生成器都是 Iterator
对象,但 list
、 dict
、 str
虽然是 Iterable
,却不是 Iterator
。
可以使用 iter()
函数创建迭代器。
2、类作为一个迭代器
把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__()
与 __next__()
。
类都有一个构造函数,Python 的构造函数为 __init__()
, 它会在对象初始化的时候执行。
1、
__iter__()
方法返回一个特殊的迭代器对象, 这个迭代器对象实现了__next__()
方法并通过 StopIteration 异常标识迭代的完成。
2、__next__()
方法会返回下一个迭代器对象。
3、创建一个返回数字的迭代器,初始值为 1,逐步递增 1。
class MyNumbers:
def __iter__(self):
self.a = 1
return self
def __next__(self):
x = self.a
self.a += 1
return x
myclass = MyNumbers()
myiter = iter(myclass)
print(next(myiter))
print(next(myiter))
3、迭代器是惰性计算
1、凡是可作用于for循环的对象都是Iterable类型
2、凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列
3、集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象
4、Python的for循环本质上就是通过不断调用next()函数实现的,但是不会StopIteration
异常