生成器生成想要的数据,可控制循环暂停,迭代器把可迭代的对象转换为生成器。( ⸝⸝⸝•_•⸝⸝⸝ )♡︎♡︎
一. 迭代、列表生成式
1.1 迭代
迭代:如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。Python的for…in循环就是迭代。list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代:
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循环中同时迭代索引和元素本身:
for i, value in enumerate(['A', 'B', 'C']):
print(i, value)
运行结果:
0 A
1 B
2 C
1.2 列表生成式
规则:for前面是一个表达式,表示将in后面的元素按照这个表达式进行计算出来后还要看in后面有没有筛选条件,
然后赋值给for…in中间的变量。格式如下:
列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
举个例子,要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11))
:
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]怎么做?方法一是循环:
L = []
for x in range(1, 11):
L.append(x * x)
print(L)
运行结果:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:
a = [x * x for x in range(1, 11)]
print(a)
运行结果:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
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能够不中断地执行完整个函数。
下面举例子说明:
下面的例子用 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:
#列表生成式
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
如果要一个个打印出来,可以通过 __next__()
获得generator的下一个返回值:
#生成器
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循环无法实现的时候,还可以用函数来实现。
#生成器
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…..
斐波那契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
#fibonacci数列
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语句处继续执行,也就是用多少,取多少,不占内存。如下:
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. 使用__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对象!
语法
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()返回值是迭代器对象。
举例子:
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