【Python】数据类型扩展 - 生成器、可迭代对象和迭代器
此文章部分转载于 https://www.cnblogs.com/wj-1314/p/8490822.html
文章目录
- 可迭代对象(Iterable)
- 迭代器(Iterator)
- 那什么是生成器
- 生成器表达式的创建
- 生成器函数的创建
- 生成器和迭代器的区别
在介绍生成器之前,先介绍可迭代对象和迭代器。
可迭代对象(Iterable)
我们已经知道,可以直接作用于for循环的数据类型有以下几种:
- 一类是集合数据类型,如list,tuple,dict,set,str等
- 一类是generator,包括生成器和带yield的generator function
这些可以直接作用于for 循环的对象统称为可迭代对象:Iterable
可以使用 isinstance() 判断一个对象是否为 Iterable对象
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False
迭代器(Iterator)
一个实现了iter方法的对象是可迭代的,一个实现next方法并且是可迭代的对象是迭代器。
可以被**next()**函数调用并不断返回下一个值的对象称为迭代器:Iterator。
所以一个实现了iter方法和next方法的对象就是迭代器。
可以使用 isinstance() 判断一个对象是否是 Iterator 对象:
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False
那什么是生成器
通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator
生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。
生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器
生成器分为生成器表达式和生成器函数,下面将会分别介绍这两个方式的创建和使用。
生成器表达式的创建
要创建一个generator,有很多种方法,第一种方法很简单,只有把一个列表生成式的[]中括号改为()小括号,就创建一个generator:
#列表生成式
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>
那么创建list和generator_ex,的区别是什么呢?从表面看就是[ ]和(),但是结果却不一样,一个打印出来是列表(因为是列表生成式),而第二个打印出来却是<generator object <genexpr> at 0x000002A4CBF9EBA0>
,那么如何打印出来generator_ex的每一个元素呢?
如果要一个个打印出来,可以通过next()函数获得generator的下一个返回值(不推荐):
#生成器
generator_ex = (x*x for x in range(10))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
结果:
0
1
4
9
16
25
36
49
64
81
Traceback (most recent call last):
File "列表生成式.py", line 42, in <module>
print(next(generator_ex))
StopIteration
从上述代码可以看出,生成器也属于迭代器,因为能够使用next()不断计算出下一个元素。且generator保存的是算法,每次调用next(generaotr_ex)就计算出他的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration的错误,而且上面这样不断调用是一个不好的习惯,正确的方法是使用for循环,因为generator也是可迭代对象:
#生成器
generator_ex = (x*x for x in range(10))
for i in generator_ex:
print(i)
结果:
0
1
4
9
16
25
36
49
64
81
所以我们创建一个generator后,基本上永远不会调用next(),而是通过for循环来迭代,并且不需要关心StopIteration的错误,generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
生成器函数的创建
生成器函数指的是函数体中包含yield关键字的函数(yield就是专门给生成器用的return)
变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次被next()调用时候从上次的返回yield语句处继续执行,也就是用多少,取多少,不占内存。
下面用生成器函数写出斐波那契数列:
斐波那契数列就是除第一个和第二个数外,任何一个数都可以由前两个相加得到:
1,1,2,3,5,8,12,21,34…
def fib(max):
n,a,b =0,0,1
while n < max:
yield b
# a,b = b ,a+b 其实相当于 t =a+b ,a =b ,b =t ,所以不必写显示写出临时变量t,就可以输出斐波那契数列的前N个数字
a,b =b,a+b
n = n+1
return 'done'
a = fib(10)
print(fib(10))
print(a.__next__())
print(a.__next__())
print(a.__next__())
print(a.__next__())
print(a.__next__())
结果:
<generator object fib at 0x0000023A21A34FC0>
1
1
2
3
5
在上面fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:
def fib(max):
n,a,b =0,0,1
while n < max:
yield b
a,b =b,a+b
n = n+1
return 'done'
for i in fib(6):
print(i)
结果:
1
1
2
3
5
8
但是用for循环调用generator时,会发现拿不到generator的return语句的返回值,因为return返回的值,只有在报StopIteration错误时才会显示。
所以如果想要拿到返回值,就必须捕获StopIteration错误,返回值包含在StopIteration的value中:
def fib(max):
n,a,b =0,0,1
while n < max:
yield b
a,b =b,a+b
n = n+1
return 'done'
g = fib(6)
while True:
try:
x = next(g)
print('generator: ',x)
except StopIteration as e:
print("生成器返回值:",e.value)
break
结果:
generator: 1
generator: 1
generator: 2
generator: 3
generator: 5
generator: 8
生成器返回值: done
生成器和迭代器的区别
看了上面生成器和迭代器的介绍,我们明白了这两个的关系是:生成器属于迭代器,迭代器包含生成器。
那么这两者有什么区别呢?其实生成器有着迭代器没有的三种属于自己的方法:
close() 方法:该方法用来关闭生成器,关闭之后再调用生成器方法将会报错
gen = (i for i in range(10))
print(next(gen))
print(next(gen))
print(gen.close())
print(next(gen))
结果:
0
1
None
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "E:\Program Files\JetBrains\PyCharm 2019.2.4\helpers\pydev\_pydev_bundle\pydev_umd.py", line 197, in runfile
pydev_imports.execfile(filename, global_vars, local_vars) # execute the script
File "E:\Program Files\JetBrains\PyCharm 2019.2.4\helpers\pydev\_pydev_imps\_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "E:/Python_selenium_advance/test.py", line 105, in <module>
print(next(gen))
调用了close()后,返回 None,并关闭了生成器,再调用next()就报错了。
send()方法,向生成器函数中的 yield 传入一个值
def gen():
i = 0
while i < 10:
# yield 用来接收send()传入的值,并将其赋值到n变量中
n = yield i
if n:
i = n
else:
i += 1
g = gen()
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))传入值1
g.send(1)
print(next(g))
print(next(g))
print(next(g))
print(next(g))
结果本来应该是输出0-10的数字,超出则报错。但因为传入的值1,于是被打断,输出的值 i 又以2开始重新输出,结果如下:
0
1
2
3
4
5
6
7
2
3
4
5
throw()方法:用来抛出一个自定义的错误,比较少用
def gen():
i = 0
while i < 5:
n = yield i
if n:
i = n
else:
i += 1
g = gen()
g.throw(NameError, ‘gen已经执行完成’)
结果:
最后总结一下:
如果你对此文有任何疑问,如果你也需要接口项目实战,如果你对软件测试、接口测试、自动化测试、面试经验交流感兴趣欢迎加入:软件测试技术群:593462778,群里的免费资料都是笔者十多年测试生涯的精华。还有同行大神一起交流技术哦。
作者:暗潮汹涌
原创不易,欢迎转载,但未经作者同意请保留此段声明,并在文章页面明显位置给出原文链接。
还没有评论,来说两句吧...