生成器生成想要的数据,可控制循环暂停,迭代器把可迭代的对象转换为生成器。( ⸝⸝⸝•_•⸝⸝⸝ )♡︎♡︎
生成器与迭代器
一. 迭代、列表生成式
1.1 迭代
迭代:如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。Python的for…in循环就是迭代。
list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代:
1 2 3 4 5 6 7 8
| d = {'a': 1, 'b': 2, 'c': 3} for key in d: print(key)
运行结果: a b c
|
- 因为dict、set的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样。
- 默认情况下,dict迭代的是key。如果要迭代value,可以用
for value in d.values()
,如果要同时迭代key和value,可以用for k, v in d.items()
。
- 当我们使用for循环时,只要作用于一个可迭代对象,for循环就可以正常运行,而我们不太关心该对象究竟是list还是其他数据类型。
最后一个小问题,如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:
1 2 3 4 5 6 7
| for i, value in enumerate(['A', 'B', 'C']): print(i, value)
运行结果: 0 A 1 B 2 C
|
1.2 列表生成式
规则:for前面是一个表达式,表示将in后面的元素按照这个表达式进行计算出来后还要看in后面有没有筛选条件,
然后赋值给for…in中间的变量。格式如下:
表达式1 for 变量 in 可迭代对象 [表达式2]
列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
举个例子,要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11))
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| for a in range(1,11): print(a)
b = list(range(1,11)) print(b)
运行结果: 1 2 3 4 5 6 7 8 9 10 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
但如果要生成[1x1, 2x2, 3x3, …, 10x10]怎么做?方法一是循环:
1 2 3 4 5 6 7
| L = [] for x in range(1, 11): L.append(x * x) print(L)
运行结果: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
|
但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:
1 2 3 4 5
| a = [x * x for x in range(1, 11)] print(a)
运行结果: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
|
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
| for前面是一个表达式,表示将in后面的元素按照这个表达式进行计算出来后还要看in后面有没有筛选条件, 然后赋值给for...in中间的变量。格式如下:
表达式1 for 变量 in 可迭代对象 [表达式2]
------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------
1. for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:
a = [x * x for x in range(1, 11) if x % 2 == 0] print(a)
运行结果: [4, 16, 36, 64, 100]
------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------
2. 还可以使用两层循环,可以生成全排列:
b = [m + n for m in 'ABC' for n in 'XYZ'] print(b)
运行结果: ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------
3. for循环其实可以同时使用两个甚至多个变量,比如dict的items()可以同时迭代key和value:
d = {'x': 'A', 'y': 'B', 'z': 'C' } a = [k + '=' + v for k, v in d.items()] print(a)
运行结果: ['x=A', 'y=B', 'z=C']
|
二. 生成器
参考资料:
《python 生成器和迭代器有这篇就够了》
《Python3 迭代器与生成器》
《Python yield 使用浅析》
什么是生成器?
通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator
生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。
生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器
python提供了两种生成器基本的方式
生成器函数:也是用def定义的,利用关键字yield一次返回一个结果、阻塞,重新开始
生成器表达式:返回一个对象,这个对象只有在需要的时候才产生结果
2.1 yield、__next__()、send()
带有yield语句的函数不再是一个普通的函数,而是一个生成器generator,可用于迭代。
yield是一个类似return 的关键字,返回一个值或者表达式,迭代一次遇到yield的时候就返回yield后面(代码块)或者右面(单行)的值,然后暂停。
一个函数正常执行遇到yeild时,yeild返回一个值给函数的调用者,然后在这暂停并记住这个位置(因为此时程序要跳转到调用这个函数的地方 => 因为yeild的返回)!不去执行下一个语句的代码。当程序执行遇到__next__()方法或者next()(Python2用)时,继续执行上次yield的下一个语句直到遇到下一个yield或者该函数结束。
send()和next()的区别就在于send可传递参数给yield表达式,这时候传递的参数就会作为yield表达式的值,而yield的参数是返回给调用者的值,也就是说send可以强行修改上一个yield表达式值。
send()和next()都有返回值,他们的返回值是当前迭代遇到的yield的时候,yield后面表达式的值,其实就是当前迭代yield后面的参数。
for…in 循环中会自动调用 next()。这就说明for…in能够不中断地执行完整个函数。
下面举例子说明:
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
| 下面的例子用 a.__next__() 和 print(next(a)) 来说明yeild的返回和暂停
------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------ 例1:
def odd(): print('step 1') yield 1 print('step 2') yield 3 print('step 3') yield 5
a = odd() b = a.__next__() print('*****分割线*****') print(b)
print('\n')
c = a.__next__() print('*****分割线*****') print(c)
输出: step 1 *****分割线***** 1
step 2 *****分割线***** 3
结论:这个时候在yield 1处暂停了(输出step1),并且yield返回了一个值1给a.__next__() 。
|
2.2 表达式创建生成器
generator保存的是算法,每次调用next(generaotr_ex)就计算出他的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration的错误。
要创建一个generator,有很多种方法,第一种方法很简单,只有把一个列表生成式的[]中括号改为()小括号,就创建一个generator:
1 2 3 4 5 6 7 8 9 10
| 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>
|
那么创建lis和generator_ex,的区别是什么呢?从表面看就是[ ]和(),但是结果却不一样,一个打印出来是列表(因为是列表生成式),而第二个打印出来却是<generator object at 0x000002A4CBF9EBA0>,那么如何打印出来generator_ex的每一个元素呢?
如果要一个个打印出来,可以通过 __next__()
获得generator的下一个返回值:
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
| generator_ex = (x*x for x in range(10)) print(generator_ex.__next__()) print(generator_ex.__next__()) print(generator_ex.__next__()) print(generator_ex.__next__()) print(generator_ex.__next__()) print(generator_ex.__next__()) print(generator_ex.__next__()) print(generator_ex.__next__()) print(generator_ex.__next__()) print(generator_ex.__next__()) print(generator_ex.__next__())
结果: 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
|
generator保存的是算法,每次调用next(generaotr_ex)就计算出他的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration的错误,而且上面这样不断调用是一个不好的习惯,正确的方法是使用for循环,因为generator也是可迭代对象。
所以我们创建一个generator后,基本上永远不会调用__next__()
,而是通过for循环来迭代,并且不需要关心StopIteration的错误,generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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
|
2.2 函数创建生成器
如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个生成器。
下面举例子说明:
著名的斐波那契数列,除第一个和第二个数外,任何一个数都可以由前两个相加得到:
1,1,2,3,5,8,13,21,34…..
斐波那契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
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
| def fib(max): n,a,b =0,0,1 while n < max: a,b =b,a+b n = n+1 print(a) return 'done' a = fib(6) print(fib(6))
运行结果: 1 1 2 3 5 8 1 1 2 3 5 8 done
|
a,b = b ,a+b 其实相当于 t =a+b ,a =b ,b =t ,所以不必写显示写出临时变量t,就可以输出斐波那契数列的前N个数字。
上面我们发现,print(b)每次函数运行都要打印,占内存,所以为了不占内存,我们也可以使用生成器,同样是使用函数,只不过函数中有 yield
语句,所以叫做生成器。但是返回的不再是一个值,而是一个生成器,和上面的例子一样。
那么这样就不占内存了,这里说一下generator和函数的执行流程,函数是顺序执行的,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用__next__()方法的时候执行,遇到yield语句返回,再次被__next__() 调用时候从上次的返回yield语句处继续执行,也就是用多少,取多少,不占内存。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| def fib(max): n,a,b =0,0,1 while n < max: yield b a,b =b,a+b n = n+1 return 'done' a = fib(6) print(fib(6))
运行结果: <generator object fib at 0x000001C03AC34FC0>
|
下面用3个例子说明用for…in 循环的好处,但是拿不到return 语句的返回值,那么就会报错,所以为了不让报错,就要进行异常处理,拿到返回值,如果想要拿到返回值,必须捕获StopIteration错误,而返回值包含在StopIteration的value中。
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| 1. 使用__next__()方法到最后一个的下一个时会报错。
def fib(max): n,a,b =0,0,1 while n < max: yield b a,b =b,a+b n = n+1 return 'done' a = fib(6) print(fib(6)) print(a.__next__()) print(a.__next__()) print(a.__next__()) print("可以顺便干其他事情") print(a.__next__()) print(a.__next__()) print(a.__next__()) print(a.__next__()) 结果: <generator object fib at 0x01058B70> 1 1 2 可以顺便干其他事情 3 5 8 Traceback (most recent call last): //看到报错,并且 StopIteration: done File "3.py", line 18, in <module> print(a.__next__()) StopIteration: done
------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------
2. 在上面fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环, 不然就会产生一个无限数列出来。把函数改成generator后,我们基本上从来不会用next()来获取下一个 返回值,而是直接使用for循环来迭代,但是拿不到return 语句的返回值:
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 //程序没报错,但是拿不到return返回的值。
------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------
3. 如果想要拿到返回值,必须捕获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 //拿到了return的返回值!
|
三. 迭代器
迭代就是循环,迭代器功能是把一个可迭代的对象转换为生成器。因为生成器本身就是可迭代的。
迭代器包含有next方法的实现,在正确的范围内返回期待的数据以及超出范围后能够抛出StopIteration的错误停止迭代。
可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型:如list,tuple,dict,set,str等;
一类是generator:包括生成器和带yield的generator 函数。
3.1 可迭代对象
这些可以直接作用于for 循环的对象统称为可迭代对象:Iterable
可以使用 isinstance()
判断一个对象是否为可Iterable对象!
查阅:[Python isinstance() 函数]
语法
isinstance(object, classinfo)
参数
object – 实例对象。
classinfo – 可以是直接或间接类名、基本类型或者由它们组成的元组。
返回值
如果两个参数类型(classinfo)相同则返回 True,否则返回 False。
3.2 迭代器
一个实现了iter方法的对象时可迭代的,一个实现next方法的对象是迭代器
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用isinstance()判断一个对象是否是Iterator对象
所以,整体的流程为:
使用isinstance()判断一个对象是否为Iterable对象(可迭代对象),语法为isinstance(对象,Iterable)
。返回Ture/False。
如果返回 Ture,使用 iter(可迭代对象)
即可得到返回值为生成器的东西。
然后就可以把这个返回值作为生成器去尽情的使用了。
查阅:[Python iter() 函数]
总结:
- 凡是可作用于for循环的对象都是Iterable类型;
- 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
- 生成器都是Iterator对象,但list、dict、str虽然是Iterable(可迭代对象),却不是Iterator(迭代器)。
- iter()返回值是迭代器对象。
举例子:
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
| from collections.abc import Iterable,Iterator print(isinstance('abc', Iterable)) print(isinstance([], Iterable)) print(isinstance({}, Iterable)) print(isinstance((x for x in range(10)), Iterable)) print(isinstance(100, Iterable))
print('\n')
print(isinstance((x for x in range(10)), Iterator)) print(isinstance([], Iterator)) print(isinstance({}, Iterator)) print(isinstance('abc', Iterator))
print('\n')
print(isinstance(iter([]), Iterator)) print(isinstance(iter('abc'), Iterator))
运行结果: True True True True False
True False False False
True True
|