Python-生成器与迭代器

生成器生成想要的数据,可控制循环暂停,迭代器把可迭代的对象转换为生成器。( ⸝⸝⸝•_•⸝⸝⸝ )♡︎♡︎



生成器与迭代器



一. 迭代、列表生成式

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


  1. 因为dict、set的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样。
  2. 默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()
  3. 当我们使用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中间的变量。格式如下:

表达式1 for 变量 in 可迭代对象 [表达式2]


列表生成式即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()


  1. 带有yield语句的函数不再是一个普通的函数,而是一个生成器generator,可用于迭代。

  2. yield是一个类似return 的关键字,返回一个值或者表达式,迭代一次遇到yield的时候就返回yield后面(代码块)或者右面(单行)的值,然后暂停。

  3. 一个函数正常执行遇到yeild时,yeild返回一个值给函数的调用者,然后在这暂停并记住这个位置(因为此时程序要跳转到调用这个函数的地方 => 因为yeild的返回)!不去执行下一个语句的代码。当程序执行遇到__next__()方法或者next()(Python2用)时,继续执行上次yield的下一个语句直到遇到下一个yield或者该函数结束。

  4. send()和next()的区别就在于send可传递参数给yield表达式,这时候传递的参数就会作为yield表达式的值,而yield的参数是返回给调用者的值,也就是说send可以强行修改上一个yield表达式值。

  5. send()和next()都有返回值,他们的返回值是当前迭代遇到的yield的时候,yield后面表达式的值,其实就是当前迭代yield后面的参数。

  6. for…in 循环中会自动调用 next()。这就说明for…in能够不中断地执行完整个函数。


下面举例子说明:

下面的例子用 a.__next__()print(next(a)) 来说明yeild的返回和暂停

------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------1def 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 at 0x000002A4CBF9EBA0>,那么如何打印出来generator_ex的每一个元素呢?

  如果要一个个打印出来,可以通过 __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对象!

查阅:[Python isinstance() 函数]


语法

isinstance(object, classinfo)

参数
object – 实例对象。
classinfo – 可以是直接或间接类名、基本类型或者由它们组成的元组。

返回值
如果两个参数类型(classinfo)相同则返回 True,否则返回 False。



3.2 迭代器

一个实现了iter方法的对象时可迭代的,一个实现next方法的对象是迭代器
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator
可以使用isinstance()判断一个对象是否是Iterator对象

所以,整体的流程为:

  1. 使用isinstance()判断一个对象是否为Iterable对象(可迭代对象),语法为isinstance(对象,Iterable)。返回Ture/False。

  2. 如果返回 Ture,使用 iter(可迭代对象) 即可得到返回值为生成器的东西。

  3. 然后就可以把这个返回值作为生成器去尽情的使用了。


查阅:[Python iter() 函数]

总结:

  1. 凡是可作用于for循环的对象都是Iterable类型;
  2. 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
    集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
  3. 生成器都是Iterator对象,但list、dict、str虽然是Iterable(可迭代对象),却不是Iterator(迭代器)。
  4. 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

  Reprint please specify: Treecatee Python-生成器与迭代器

 Previous
Python-模块和包 Python-模块和包
from…import…😀 模块和包 一. 模块与包的意义 1.1 什么是模块? 在Python中,一个.py文件就称之为一个模块(Module)。 1.2 为什么要使用模块? 为了编写可维护的代码,我们把很多函数分组,分
2019-03-03
Next 
Python-函数 Python-函数
有两种定义函数的方法,千万不要忘了冒号和return ( ´⚰︎` ) 函数 函数分为:内置(系统)函数和自定义函数。 自定义函数又分为普通(有名字的)函数和匿名(无名字的)函数,二者定义方式不一样; 自定义和函数里面
2019-03-02
  TOC