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

你的名字 2023-07-05 11:48 38阅读 0赞

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

文章目录

      • 可迭代对象(Iterable)
      • 迭代器(Iterator)
      • 那什么是生成器
        • 生成器表达式的创建
        • 生成器函数的创建
      • 生成器和迭代器的区别

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

可迭代对象(Iterable)

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

  1. 一类是集合数据类型,如list,tuple,dict,set,str等
  2. 一类是generator,包括生成器和带yield的generator function

这些可以直接作用于for 循环的对象统称为可迭代对象:Iterable

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

  1. >>> from collections import Iterable
  2. >>> isinstance([], Iterable)
  3. True
  4. >>> isinstance({}, Iterable)
  5. True
  6. >>> isinstance('abc', Iterable)
  7. True
  8. >>> isinstance((x for x in range(10)), Iterable)
  9. True
  10. >>> isinstance(100, Iterable)
  11. False

迭代器(Iterator)

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

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

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

可以使用 isinstance() 判断一个对象是否是 Iterator 对象:

  1. >>> from collections import Iterator
  2. >>> isinstance((x for x in range(10)), Iterator)
  3. True
  4. >>> isinstance([], Iterator)
  5. False
  6. >>> isinstance({}, Iterator)
  7. False
  8. >>> isinstance('abc', Iterator)
  9. False

那什么是生成器

通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator

生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器

生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器

生成器分为生成器表达式和生成器函数,下面将会分别介绍这两个方式的创建和使用。

生成器表达式的创建

要创建一个generator,有很多种方法,第一种方法很简单,只有把一个列表生成式的[]中括号改为()小括号,就创建一个generator:

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

结果:

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

那么创建list和generator_ex,的区别是什么呢?从表面看就是[ ]和(),但是结果却不一样,一个打印出来是列表(因为是列表生成式),而第二个打印出来却是<generator object <genexpr> at 0x000002A4CBF9EBA0>,那么如何打印出来generator_ex的每一个元素呢?

如果要一个个打印出来,可以通过next()函数获得generator的下一个返回值(不推荐):

  1. #生成器
  2. generator_ex = (x*x for x in range(10))
  3. print(next(generator_ex))
  4. print(next(generator_ex))
  5. print(next(generator_ex))
  6. print(next(generator_ex))
  7. print(next(generator_ex))
  8. print(next(generator_ex))
  9. print(next(generator_ex))
  10. print(next(generator_ex))
  11. print(next(generator_ex))
  12. print(next(generator_ex))
  13. print(next(generator_ex))

结果:

  1. 0
  2. 1
  3. 4
  4. 9
  5. 16
  6. 25
  7. 36
  8. 49
  9. 64
  10. 81
  11. Traceback (most recent call last):
  12. File "列表生成式.py", line 42, in <module>
  13. print(next(generator_ex))
  14. StopIteration

从上述代码可以看出,生成器也属于迭代器,因为能够使用next()不断计算出下一个元素。且generator保存的是算法,每次调用next(generaotr_ex)就计算出他的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration的错误,而且上面这样不断调用是一个不好的习惯,正确的方法是使用for循环,因为generator也是可迭代对象:

  1. #生成器
  2. generator_ex = (x*x for x in range(10))
  3. for i in generator_ex:
  4. print(i)

结果:

  1. 0
  2. 1
  3. 4
  4. 9
  5. 16
  6. 25
  7. 36
  8. 49
  9. 64
  10. 81

所以我们创建一个generator后,基本上永远不会调用next(),而是通过for循环来迭代,并且不需要关心StopIteration的错误,generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

生成器函数的创建

生成器函数指的是函数体中包含yield关键字的函数(yield就是专门给生成器用的return
变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次被next()调用时候从上次的返回yield语句处继续执行,也就是用多少,取多少,不占内存。

下面用生成器函数写出斐波那契数列:

斐波那契数列就是除第一个和第二个数外,任何一个数都可以由前两个相加得到:

1,1,2,3,5,8,12,21,34…

  1. def fib(max):
  2. n,a,b =0,0,1
  3. while n < max:
  4. yield b
  5. # a,b = b ,a+b 其实相当于 t =a+b ,a =b ,b =t ,所以不必写显示写出临时变量t,就可以输出斐波那契数列的前N个数字
  6. a,b =b,a+b
  7. n = n+1
  8. return 'done'
  9. a = fib(10)
  10. print(fib(10))
  11. print(a.__next__())
  12. print(a.__next__())
  13. print(a.__next__())
  14. print(a.__next__())
  15. print(a.__next__())

结果:

  1. <generator object fib at 0x0000023A21A34FC0>
  2. 1
  3. 1
  4. 2
  5. 3
  6. 5

在上面fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

  1. def fib(max):
  2. n,a,b =0,0,1
  3. while n < max:
  4. yield b
  5. a,b =b,a+b
  6. n = n+1
  7. return 'done'
  8. for i in fib(6):
  9. print(i)

结果:

  1. 1
  2. 1
  3. 2
  4. 3
  5. 5
  6. 8

但是用for循环调用generator时,会发现拿不到generator的return语句的返回值,因为return返回的值,只有在报StopIteration错误时才会显示。
所以如果想要拿到返回值,就必须捕获StopIteration错误,返回值包含在StopIteration的value中:

  1. def fib(max):
  2. n,a,b =0,0,1
  3. while n < max:
  4. yield b
  5. a,b =b,a+b
  6. n = n+1
  7. return 'done'
  8. g = fib(6)
  9. while True:
  10. try:
  11. x = next(g)
  12. print('generator: ',x)
  13. except StopIteration as e:
  14. print("生成器返回值:",e.value)
  15. break

结果:

  1. generator: 1
  2. generator: 1
  3. generator: 2
  4. generator: 3
  5. generator: 5
  6. generator: 8
  7. 生成器返回值: done

生成器和迭代器的区别

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

  1. close() 方法:该方法用来关闭生成器,关闭之后再调用生成器方法将会报错

    gen = (i for i in range(10))
    print(next(gen))
    print(next(gen))
    print(gen.close())
    print(next(gen))

结果:

  1. 0
  2. 1
  3. None
  4. Traceback (most recent call last):
  5. File "<input>", line 1, in <module>
  6. File "E:\Program Files\JetBrains\PyCharm 2019.2.4\helpers\pydev\_pydev_bundle\pydev_umd.py", line 197, in runfile
  7. pydev_imports.execfile(filename, global_vars, local_vars) # execute the script
  8. File "E:\Program Files\JetBrains\PyCharm 2019.2.4\helpers\pydev\_pydev_imps\_pydev_execfile.py", line 18, in execfile
  9. exec(compile(contents+"\n", file, 'exec'), glob, loc)
  10. File "E:/Python_selenium_advance/test.py", line 105, in <module>
  11. print(next(gen))

调用了close()后,返回 None,并关闭了生成器,再调用next()就报错了。

  1. send()方法,向生成器函数中的 yield 传入一个值

    def gen():

    1. i = 0
    2. while i < 10:
    3. # yield 用来接收send()传入的值,并将其赋值到n变量中
    4. n = yield i
    5. if n:
    6. i = n
    7. else:
    8. 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开始重新输出,结果如下:

  1. 0
  2. 1
  3. 2
  4. 3
  5. 4
  6. 5
  7. 6
  8. 7
  9. 2
  10. 3
  11. 4
  12. 5
  1. throw()方法:用来抛出一个自定义的错误,比较少用

    def gen():

    1. i = 0
    2. while i < 5:
    3. n = yield i
    4. if n:
    5. i = n
    6. else:
    7. i += 1

    g = gen()
    g.throw(NameError, ‘gen已经执行完成’)

结果:
在这里插入图片描述

最后总结一下:
如果你对此文有任何疑问,如果你也需要接口项目实战,如果你对软件测试、接口测试、自动化测试、面试经验交流感兴趣欢迎加入:软件测试技术群:593462778,群里的免费资料都是笔者十多年测试生涯的精华。还有同行大神一起交流技术哦。

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

发表评论

表情:
评论列表 (有 0 条评论,38人围观)

还没有评论,来说两句吧...

相关阅读