15 Python 迭代器和生成器

什么是迭代 (iterable)

  字符串、列表、元组、字典、集合都可以被for循环,说明他们都是可迭代的

  可以直接作用于for循环的对象统称为可迭代对象(Iterable)。

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

  所有的Iterable均可以通过内置函数iter()来转变为Iterator。

  对迭代器来讲,有一个__next__()就够了。在你使用for 和 in 语句时,程序就会自动调用即将被处理的对象的迭代器对象,然后使用它的__next__()方法,直到监测到一个StopIteration异常。

  可迭代协议

    我们现在是从结果分析原因,能被for循环的就是“可迭代的”,但是如果正着想,for怎么知道谁是可迭代的呢?

    假如我们自己写了一个数据类型,希望这个数据类型里的东西也可以使用for被一个一个的取出来,那我们就必须满足for的要求。这个要求就叫做“协议”。

  可以被迭代要满足的要求就叫做可迭代协议。可迭代协议的定义非常简单,就是内部实现了__iter__方法。

 1 >>> L = [1,2,3]
 2 >>> [x**2 for x in L]
 3 [1, 4, 9]
 4 >>> next(L)
 5 Traceback (most recent call last):
 6   File "<stdin>", line 1, in <module>
 7 TypeError: 'list' object is not an iterator
 8 >>> I=iter(L)
 9 >>> next(I)
10 1
11 >>> next(I)
12 2
13 >>> next(I)
14 3
15 >>> next(I)
16 Traceback (most recent call last):
17   File "<stdin>", line 1, in <module>
18 StopIteration
View Code

  上面例子中,列表L可以被for进行循环但是不能被内置函数next()用来查找下一个值,所以L是Iterable。

  L通过iter进行包装后设为I,I可以被next()用来查找下一个值,所以I是Iterator。

  1. 内置函数iter()仅仅是调用了对象的__iter()方法,所以list对象内部一定存在方法iter__()
  2. 内置函数next()仅仅是调用了对象的__next()方法,所以list对象内部一定不存在方法next__(),但是Itrator中一定存在这个方法。
  3. for循环内部事实上就是先调用iter()把Iterable变成Iterator在进行循环迭代的
 1 >>> L = [4,5,6]
 2 >>> I = L.__iter__()
 3 >>> L.__next__()
 4 Traceback (most recent call last):
 5   File "<stdin>", line 1, in <module>
 6 AttributeError: 'list' object has no attribute '__next__'
 7 >>> I.__next__()
 8 4
 9 >>> from collections import Iterator, Iterable
10 >>> isinstance(L, Iterable)
11 True
12 >>> isinstance(L, Iterator)
13 False
14 >>> isinstance(I, Iterable)
15 True
16 >>> isinstance(I, Iterator)
17 True
18 >>> [x**2 for x in I]    
19 [25, 36]
View Code

迭代器

迭代器遵循迭代器协议:必须拥有__iter__方法和__next__方法。

具有访问生成器的能力,可以访问到生成器的值,类似于生成器的__next__方法,一个一个值一个值得去迭代,只能够按照顺序的去查找。

特点:

  1. 访问者不需要关心迭代器内部的结构,仅需通过next()方法不断去取下一个内容
  2. 不能随机访问集合中的某个值 ,只能从头到尾依次访问
  3. 访问到一半时不能往回退
  4. 便于循环比较大的数据集合,节省内存
1 print('__next__' in dir(range(12)))  #查看'__next__'是不是在range()方法执行之后内部是否有__next__
2 print('__iter__' in dir(range(12)))  #查看'__next__'是不是在range()方法执行之后内部是否有__next__
3 
4 from collections import Iterator
5 print(isinstance(range(100000000),Iterator))  #验证range执行之后得到的结果不是一个迭代器
View Code

yield from

 1 def gen1():
 2     for c in 'AB':
 3         yield c
 4     for i in range(3):
 5         yield i
 6 
 7 print(list(gen1()))
 8 
 9 def gen2():
10     yield from 'AB'
11     yield from range(3)
12 
13 print(list(gen2()))
View Code

生成器

  1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行

  2.生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表

 

生成器Generator:

  本质:迭代器(所以自带了__iter__方法和__next__方法,不需要我们去实现)

  特点:惰性运算,开发者自定义

生成器函数

  一个包含yield关键字的函数就是一个生成器函数。yield可以为我们从函数中返回值,但是yield又不同于return,return的执行意味着程序的结束,调用生成器函数不会得到返回的具体的值,而是得到一个可迭代的对象。每一次获取这个可迭代对象的值,就能推动函数的执行,获取新的返回值。直到函数执行结束。

  仅仅拥有生成某种东西的能力,如果不用__next__方法是获取不到值得。

  创建一个生成器函数

 1 >>> def scq():
 2     print("11")
 3        # 当函数代码块中遇到yield关键字的时候,这个函数就是一个生成器函数
 4     yield 1
 5     print("22")
 6     yield 2
 7     print("33")
 8     yield 3
 9 
10 
11 # 把生成器赋值给一个对象
12 
13 >>> r = scq()
14 
15 # 查看r的苏剧类型并且输出r的值
16 
17 >>> print(type(r),r)
18 <class 'generator'> <generator object scq at 0x000001F117D8DF10> 
19 
20 # 当执行生成器的__next__的时候,代码会按照顺序去执行,当执行到yield时会返回并
21 # 提出,yield后面的值就是返回值,然后记录代码执行的位置,并退出
22 
23 >>> ret = r.__next__()
24 11
25 
26 # 第二次执行的时候会根据上次代码执行的位置继续往下执行
27 
28 >>> ret = r.__next__()
29 22
30 >>> ret = r.__next__()
31 33
32 
33 # 如果__next__获取不到值的时候就会报StopIteration错误
34 
35 >>> ret = r.__next__()
36 Traceback (most recent call last):
37   File "<stdin>", line 1, in <module>
38 StopIteration
View Code

  利用生成器创建一个range

 1 # 创建一个生成器函数,函数名是range,n是传入的参数,也是输出的数的最大值
 2 def range(n):
 3     # 默认从0开始
 4     start = 0
 5     # 进入while循环,如果最小值小于最大值就进入循环
 6     while start < n:
 7         # 第一次返回start,下面代码不执行
 8         yield start
 9         # 第二次进来的时候start = start + 1,然后进入下一次循环
10         start += 1
11 
12 # 停止的参数为5
13 obj = range(5)
14 # 第一个数赋值给n1
15 n1 = obj.__next__()
16 # 第二个数赋值给n2
17 n2 = obj.__next__()
18 # 第三个数赋值给n3
19 n3 = obj.__next__()
20 # 第四个数赋值给n4
21 n4 = obj.__next__()
22 # 第五个数赋值给n5
23 n5 = obj.__next__()
24 
25 # 输出这五个数的值
26 print(n1,n2,n3,n4,n5)
27 
28 # 执行结果
29 
30 C:Python35python.exe F:/Python_code/sublime/Week5/Day03/s1.py
31 0 1 2 3 4
32 
33 Process finished with exit code 0
View Code

  生成器监听文件输入的例题

 1 import time
 2 
 3 
 4 def tail(filename):
 5     f = open(filename)
 6     f.seek(0, 2) #从文件末尾算起
 7     while True:
 8         line = f.readline()  # 读取文件中新的文本行
 9         if not line:
10             time.sleep(0.1)
11             continue
12         yield line
13 
14 tail_g = tail('tmp')
15 for line in tail_g:
16     print(line)
View Code

 计算移动平均值(1)

 1 def averager():
 2     total = 0.0
 3     count = 0
 4     average = None
 5     while True:
 6         term = yield average
 7         total += term
 8         count += 1
 9         average = total/count
10 
11 
12 g_avg = averager()
13 next(g_avg)
14 print(g_avg.send(10))
15 print(g_avg.send(30))
16 print(g_avg.send(5))
View Code

 计算移动平均值(2)预激协程的装饰器

 1 def init(func):  #在调用被装饰生成器函数的时候首先用next激活生成器
 2     def inner(*args,**kwargs):
 3         g = func(*args,**kwargs)
 4         next(g)
 5         return g
 6     return inner
 7 
 8 @init
 9 def averager():
10     total = 0.0
11     count = 0
12     average = None
13     while True:
14         term = yield average
15         total += term
16         count += 1
17         average = total/count
18 
19 
20 g_avg = averager()
21 # next(g_avg)   在装饰器中执行了next方法
22 print(g_avg.send(10))
23 print(g_avg.send(30))
24 print(g_avg.send(5))
View Code

列表推导式和生成器表达式

 1 #老男孩由于峰哥的强势加盟很快走上了上市之路,alex思来想去决定下几个鸡蛋来报答峰哥
 2 
 3 egg_list=['鸡蛋%s' %i for i in range(10)] #列表解析
 4 
 5 #峰哥瞅着alex下的一筐鸡蛋,捂住了鼻子,说了句:哥,你还是给我只母鸡吧,我自己回家下
 6 
 7 laomuji=('鸡蛋%s' %i for i in range(10))#生成器表达式
 8 print(laomuji)
 9 print(next(laomuji)) #next本质就是调用__next__
10 print(laomuji.__next__())
11 print(next(laomuji))
View Code

各种推导式详解

  推导式的套路

    之前我们已经学习了最简单的列表推导式和生成器表达式。但是除此之外,其实还有字典推导式、集合推导式等等。

    下面是一个以列表推导式为例的推导式详细格式,同样适用于其他推导式。

1 variable = [out_exp_res for out_exp in input_list if out_exp == 2]
2   out_exp_res:  列表生成元素表达式,可以是有返回值的函数。
3   for out_exp in input_list:  迭代input_list将out_exp传入out_exp_res表达式中。
4   if out_exp == 2:  根据条件过滤哪些值可以。
View Code

列表推导式

  例一:30以内所有能被3整除的数

1 multiples = [i for i in range(30) if i % 3 is 0]
2 print(multiples)
3 # Output: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
View Code

  例二:30以内所有能被3整除的数的平方

1 def squared(x):
2     return x*x
3 multiples = [squared(i) for i in range(30) if i % 3 is 0]
4 print(multiples)
View Code

  例三:找到嵌套列表中名字含有两个‘e’的所有名字

1 names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],
2          ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']]
3 
4 print([name for lst in names for name in lst if name.count('e') >= 2])  # 注意遍历顺序,这是实现的关键
View Code

字典推导式

  例一:将一个字典的key和value对调

1 mcase = {'a': 10, 'b': 34}
2 mcase_frequency = {mcase[k]: k for k in mcase}
3 print(mcase_frequency)
View Code

  例二:合并大小写对应的value值,将k统一成小写

1 mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3}
2 mcase_frequency = {k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase.keys()}
3 print(mcase_frequency)
View Code

集合推导式

  例:计算列表中每个值的平方,自带去重功能

1 squared = {x**2 for x in [1, -1, 2]}
2 print(squared)
3 # Output: set([1, 4])
View Code

练习题:

  例1:  过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母

  例2:  求(x,y)其中x是0-5之间的偶数,y是0-5之间的奇数组成的元祖列表

  例3:  求M中3,6,9组成的列表M = [[1,2,3],[4,5,6],[7,8,9]]

1 1.[name.upper() for name in names if len(name)>3] 
2 2.[(x,y) for x in range(5) if x%2==0 for y in range(5) if y %2==1] 
3 3. [row[2] for row in M]
View Code

总结:

  1.把列表解析的[]换成()得到的就是生成器表达式

  2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存

  3.Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:

1 sum(x ** 2 for x in range(4))
2 
3 # 而不用多此一举的先构造一个列表:
4 
5 sum([x ** 2 for x in range(4)]) 
View Code

可迭代对象:

  拥有__iter__方法

  特点:惰性运算

  例如:range(),str,list,tuple,dict,set

迭代器Iterator:

  拥有__iter__方法和__next__方法

  例如:iter(range()),iter(str),iter(list),iter(tuple),iter(dict),iter(set),reversed(list_o),map(func,list_o),filter(func,list_o),file_o

生成器Generator:

  本质:迭代器,所以拥有__iter__方法和__next__方法

  特点:惰性运算,开发者自定义

使用生成器的优点:

1.延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。

2.提高代码可读性

1 #列表解析
2 sum([i for i in range(100000000)])#内存占用大,机器容易卡死
3  
4 #生成器表达式
5 sum(i for i in range(100000000))#几乎不占内存
View Code

生成器相关的面试题

  生成器在编程中发生了很多的作用,善用生成器可以帮助我们解决很多复杂的问题

  除此之外,生成器也是面试题中的重点,在完成一些功能之外,人们也想出了很多魔性的面试题。
  面试题(1)

 1 def demo():
 2     for i in range(4):
 3         yield i
 4 
 5 g=demo()
 6 
 7 g1=(i for i in g)
 8 g2=(i for i in g1)
 9 
10 print(list(g1))
11 print(list(g2))
View Code

  面试题(2)

 1 def add(n,i):
 2     return n+i
 3 
 4 def test():
 5     for i in range(4):
 6         yield i
 7 
 8 g=test()
 9 for n in [1,10]:
10     g=(add(n,i) for i in g)
11 
12 print(list(g))
View Code

  tail&grep

 1 def demo():
 2     for i in range(4):
 3         yield i
 4 
 5 g=demo()
 6 
 7 g1=(i for i in g)
 8 g2=(i for i in g1)
 9 
10 print(list(g1))
11 print(list(g2))
12 
13 复制代码
14 复制代码
15 
16 def add(n,i):
17     return n+i
18 
19 def test():
20     for i in range(4):
21         yield i
22 
23 g=test()
24 for n in [1,10]:
25     g=(add(n,i) for i in g)
26 
27 print(list(g))
28 
29 复制代码
30 复制代码
31 
32 import os
33 
34 def init(func):
35     def wrapper(*args,**kwargs):
36         g=func(*args,**kwargs)
37         next(g)
38         return g
39     return wrapper
40 
41 @init
42 def list_files(target):
43     while 1:
44         dir_to_search=yield
45         for top_dir,dir,files in os.walk(dir_to_search):
46             for file in files:
47                 target.send(os.path.join(top_dir,file))
48 @init
49 def opener(target):
50     while 1:
51         file=yield
52         fn=open(file)
53         target.send((file,fn))
54 @init
55 def cat(target):
56     while 1:
57         file,fn=yield
58         for line in fn:
59             target.send((file,line))
60 
61 @init
62 def grep(pattern,target):
63     while 1:
64         file,line=yield
65         if pattern in line:
66             target.send(file)
67 @init
68 def printer():
69     while 1:
70         file=yield
71         if file:
72             print(file)
73 
74 g=list_files(opener(cat(grep('python',printer()))))
75 
76 g.send('/test1')
77 
78 协程应用:grep -rl /dir
View Code
原文地址:https://www.cnblogs.com/panfb/p/7811270.html