【Python】数据类型扩展 - 生成器、可迭代对象和迭代器

此文章部分转载于 https://www.cnblogs.com/wj-1314/p/8490822.html


在介绍生成器之前,先介绍可迭代对象和迭代器。

可迭代对象(Iterable)

我们已经知道,可以直接作用于for循环的数据类型有以下几种:

  1. 一类是集合数据类型,如list,tuple,dict,set,str等

  2. 一类是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

生成器和迭代器的区别

看了上面生成器和迭代器的介绍,我们明白了这两个的关系是:生成器属于迭代器,迭代器包含生成器。
那么这两者有什么区别呢?其实生成器有着迭代器没有的三种属于自己的方法:

  1. 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()就报错了。

  1. 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
  1. 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,群里的免费资料都是笔者十多年测试生涯的精华。还有同行大神一起交流技术哦。

作者:暗潮汹涌
原创不易,欢迎转载,但未经作者同意请保留此段声明,并在文章页面明显位置给出原文链接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值