day4-python之迭代器和生成器

 

1、引言python

有一个列表l = ['a','b','c','d','e'],想取列表中内容有几种方式?程序员

一、索引取值l[0]面试

二、for循环取值编程

那么,索引取值和for循环取值的区别是什么。数组

若是索引取值必需要知道这个值在什么位置。app

若是for循环取值,把每一个值都取到,不须要关心每一个值的位置,由于for循环只能按顺序取值,不能跳过任何一个值直接去取其余位置的值。ssh

可是for循环的内部是怎么工做的呢?为何可使用for循环来取值?ide

2、迭代器函数

首先,对一个列表进行for循环,确定是没问题大数据

1 for i in [1,2,3,4]: 2     print(i)

其次,对一个数字1234进行for循环,会报错int类型不是一个iterable(可迭代的)

 1 for i in 1234:  2     print(i)  3 
 4 # 结果:
 5 '''
 6 Traceback (most recent call last):  7  File "C:/Users/benjamin/python自动化21期/day4/00 day4 test.py", line 87, in <module>  8  for i in 1234:  9 TypeError: 'int' object is not iterable 10 '''

 

一、什么叫迭代?

将某个数据集内的数据“一个挨着一个的取出来”,就叫作迭代。

 1 from collections import Iterable  2 
 3 l = [1, 2, 3, 4]  4 t = (1, 2, 3, 4)  5 d = {1: 2, 3: 4}  6 s = {1, 2, 3, 4}  7 
 8 print(isinstance(l, Iterable))  9 print(isinstance(t, Iterable)) 10 print(isinstance(d, Iterable)) 11 print(isinstance(s, Iterable))

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

二、可迭代协议

能够被迭代要求知足的就叫作可迭代协议。可迭代协议的定义就是内部实现了__iter__方法。

1 print(dir([1,2])) 2 print(dir((2,3))) 3 print(dir({1:2})) 4 print(dir({1,2}))
1 ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] 2 ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] 3 ['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values'] 4 ['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__init_subclass__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
结果

能够被for循环的都是可迭代的,要想可迭代,内部必须有一个__iter__方法。

1 print([1,2].__iter__()) 2 
3 # 结果:<list_iterator object at 0x0000026D22A59320>

获得一个list_iterator(列表迭代器)

 

三、迭代器协议

1 # dir([1,2]) 是列表中实现的全部方法,dir([1,2].__iter__())是列表迭代器中实现的全部方法,都是以列表的形式返回的。为了看得更清楚,分别将他们转换成集合,而后取差集。
2 # print(dir([1,2])) # 查看列表的全部方法
3 # print(dir([1,2].__iter__())) # 查看列表迭代器的全部方法
4 print(set(dir([1,2].__iter__()))-set(dir([1,2]))) 5 
6 # 结果:{'__setstate__', '__next__', '__length_hint__'}

三个方法的做用:

 1 iter_l = [1,2,3,4,5,6].__iter__()  2 
 3 # 获取迭代器中元素的长度
 4 print(iter_l.__length_hint__())  5 
 6 # 根据索引值指定从哪里开始迭代
 7 print('*',iter_l.__setstate__(4))  8 
 9 # 一个一个的取值
10 print('**',iter_l.__next__()) 11 print('***',iter_l.__next__())

在for循环中,就是在内部调用了__next__方法才能取到一个一个的值。

用迭代器next方法来写一个不依赖for的遍历:

 1 l = [1,2,3,4]  2 l_iter = l.__iter__()  3 item = l_iter.__next__()  4 print(item)  5 item = l_iter.__next__()  6 print(item)  7 item = l_iter.__next__()  8 print(item)  9 item = l_iter.__next__() 10 print(item) 11 item = l_iter.__next__() 12 print(item)

这里会抛出一个异常StopIteration,告诉咱们列表已经没有有效的元素。

这时,就要使用异常处理机制把这个异常处理掉。

1 l = [1,2,3,4] 2 l_iter = l.__iter__() 3 while True: 4     try: 5         item = l_iter.__next__() 6         print(item) 7     except StopIteration: 8         break

while循环从l_iter获取一个一个的值,那么l_iter就是一个迭代器。

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

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执行以后获得的结果不是一个迭代器
range函数的返回值是一个可迭代对象

python2的range无论range多少,会生成一个列表,这个列表将用来存储全部的值(工做全作完才汇报)

python3的range无论range多少,都不会实际的生成任何一个值,只有要的时候才会从迭代器里面生成(每作一步都汇报)

迭代器的优点:

        节省内存

         取一个值就能进行接下来的计算,不须要等到全部值都计算出来才开始接下来的计算 —— 快

迭代器的特性:惰性运算

列表  字典  元组  字符串  集合  range  文件句柄  enumerate

四、为何要有for循环

有了下标的访问方式,能够这样遍历一个列表:

1 l=[1,2,3] 2 
3 index=0 4 while index < len(l): 5     print(l[index]) 6     index+=1

序列类型字符串、列表、元组都有下标,能够用上面方式访问。可是非序列类型字典、集合、文件对象就不能够了。因此,for循环就是基于迭代器协议提供了一个统一的能够遍历全部对象的方法,即在遍历以前,先调用对象的__iter__方法将其转换成一个迭代器,而后使用迭代器协议去实现循环访问,这样全部的对象就均可以经过for循环来遍历。

 3、生成器

 一、迭代器有两种:

1)调用方法直接返回的

2)可迭代对象经过执行iter方法获得的

迭代器的最大好处就是节省内存,而为了节省内存,本身写的能实现迭代器功能的东西就叫作生成器。

二、python中提供的生成器:

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

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

三、生成器Generator:

本质:迭代器(因此自带了__iter__方法和__next__方法,不须要咱们去实现)

特色:惰性运算、开发者自定义

 

四、生成器函数

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

 1 import time  2 def genrator_fun1():  3     a = 1
 4     print('如今定义了a变量')  5     yield a  6     b = 2
 7     print('如今又定义了b变量')  8     yield b  9 
10 g1 = genrator_fun1() 11 print('g1 : ',g1)       #打印g1能够发现g1就是一个生成器
12 print('-'*20)   #我是华丽的分割线
13 print(next(g1)) 14 time.sleep(1)   #sleep一秒看清执行过程
15 print(next(g1))
初识生成器函数

生成器的好处,就是不会一会儿在内存中生成太多数据。

例1:生成器函数,工厂作校服

假如我想让工厂给学生作校服,生产2000000件衣服,我和工厂一说,工厂应该是先答应下来,而后再去生产,我能够一件一件的要,也能够根据学生一批一批的找工厂拿。
而不能是一说要生产2000000件衣服,工厂就先去作生产2000000件衣服,等回来作好了,学生都毕业了。。。

 1 def produce():  2     """生产衣服"""
 3     for i in range(2000000):  4         yield "生产了第%s件衣服"%i  5 
 6 product_g = produce()  7 print(product_g.__next__()) #要一件衣服
 8 print(product_g.__next__()) #再要一件衣服
 9 print(product_g.__next__()) #再要一件衣服
10 num = 0 11 for i in product_g:         #要一批衣服,好比5件
12     print(i) 13     num +=1
14     if num == 5: 15         break
16 
17 #到这里咱们找工厂拿了8件衣服,我一共让个人生产函数(也就是produce生成器函数)生产2000000件衣服。
18 #剩下的还有不少衣服,咱们能够一直拿,也能够放着等想拿的时候再拿
初识生成器二
生成器监听文件输入

五、send

 1 def generator():  2     print(123)  3     content = yield 1
 4     print('=======',content)  5     print(456)  6     yield 2
 7 
 8 g = generator()  9 ret = g.__next__() 10 print('***',ret) 11 ret = g.send('hello')   #send的效果和next同样
12 rint('***',ret) 13 
14 #send 获取下一个值的效果和next基本一致
15 #只是在获取下一个值的时候,给上一yield的位置传递一个数据
16 #使用send的注意事项
17     # 第一次使用生成器的时候 是用next获取下一个值
18     # 最后一个yield不能接受外部的值

 # 计算移动平均值

# 12  13  15  18

# 月度 的 天平均收入

 1 # 必须先用next再用send
 2 def average():  3     total=0 #总数
 4     day=0 #天数
 5     average=0 #平均数
 6     while True:  7         day_num = yield average   #average=0
 8         total += day_num  9         day += 1
10         average = total/day 11 avg=average() #直接返回生成器
12 next(avg)#激活生成器,avg.send(),什么都不传的时候send和next的效果同样
13 print(avg.send(10)) 14 print(avg.send(20))#send 1.传值 2.next
15 print(avg.send(30))
计算移动平均值
 1 # 让装饰器去激活
 2 def wrapper(func):  3     def inner(*args,**kwargs):  4        ret = func(*args,**kwargs)  5  next(ret)  6        return ret  7     return inner  8 
 9 @wrapper 10 def average(): 11     total=0 #总数
12     day=0 #天数
13     average=0 #平均数
14     while True: 15         day_num = yield average   #average=0
16         total += day_num 17         day += 1
18         average = total/day 19 
20 
21 ret=average() #直接返回生成器
22 print(ret.send(10)) 23 print(ret.send(20))#send 1.传一个值过去 2.让当前yield继续执行
24 print(ret.send(30))
带装饰器的计算移动平均值

 七、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()))
yield from

 如何从生成器中取值

一、next  随时能够中止,最后一次会报错

print(next(g))

print(next(g))

二、for循环  从头至尾遍历一次,不遇到break,函数里面不遇到return不会中止

for i in g:

  print(i)

三、list、tuple  数据类型的强转,会把全部的数据都加载到内存里,很是浪费内存

print(g)

print(list(g))

 总结:

# 生成器函数 是咱们python程序员实现迭代器的一种手段
# 主要特征是 在函数中 含有yield
# 调用一个生成器函数 不会执行这个函数中的带码 只是会得到一个生成器(迭代器)
# 只有从生成器中取值的时候,才会执行函数内部的带码,且每获取一个数据才执行获得这个数据的带码
# 获取数据的方式包括 next send 循环 数据类型的强制转化
# yield返回值的简便方法,若是自己就是循环一个可迭代的,且要把可迭代数据中的每个元素都返回 能够用yield from
# 使用send的时候,在生成器创造出来以后须要进行预激,这一步可使用装饰器完成
# 生成器的特色 : 节省内存 惰性运算
# 生成器用来解决 内存问题 和程序功能之间的解耦

4、列表推导式

 1 例1:  2 # for循环
 3 y = 2
 4 for i in range(100):  5     print( i * y )  6 
 7 # 列表推导式
 8 y = 2
 9 l = [ i * y for i in range(100)] 10 print(l) 11 
12 例2: 13 # for循环
14 l=[{'name':'v1','age':'22'},{'name':'v2'}] 15 for dic in l: 16     print(dic['name']) 17 
18 # 列表推导式
19 l=[{'name':'v1','age':'22'},{'name':'v2'}] 20 name_list=[dic['name'] for dic in l] 21 print(name_list)
列表推导式
 1 # ======一层循环======
 2 l = [i*i for i in range(1,10)]  3 print(l)  4 # 上面的列表推倒式就至关于下面的
 5 l  = []  6 for i in range(1,10):  7     l.append(i*i)  8 print(l)  9 l = [] 10 
11 
12 # ======多层循环========
13 # 1.列表推倒式
14 l = [i*j for i in range(1,10) for j in range(1,10)] 15 print(l) 16 # 2.循环
17 l = [] 18 for i in range(1,10): 19     for j in range(1,10): 20         s = i*j 21  l.append(s) 22 print(l)
列表推导式

 总结:

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

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

3)python不但使用迭代器协议,让for循环变得更加通用。大部份内置函数,也是使用迭代器协议访问对象的。例如,sum函数是python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,因此,咱们能够直接这样计算一系列值的和:

sum(x ** 2 for x in range(4))

而不用画蛇添足的先构造一个列表:

sum([x ** 2 for x in range(4)]) 

5、推导式详解

列表推导式、生成器表达式、字典推导式、集合推导式等等。

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:  根据条件过滤哪些值能够。

一、列表推导式

例1: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]
列表推导式1

例2: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)
列表推导式2

例3:找到嵌套列表中名字含有两个‘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])  # 注意遍历顺序,这是实现的关键
列表推导式3

二、字典推导式

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

1 mcase = {'a': 10, 'b': 34} 2 mcase_frequency = {mcase[k]: k for k in mcase} 3 print(mcase_frequency)
字典推导式1

例2:合并大小写对应的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)
字典推导式2

三、集合推导式

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

1 squared = {x**2 for x in [1, -1, 2]} 2 print(squared) 3 # Output: set([1, 4])
集合推导式

 练习题:

例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 [name.upper() for name in names if len(name)>3] 2 [(x,y) for x in range(5) if x%2==0 for y in range(5) if y %2==1] 3 [row[2] for row in M] 
练习题

6、总结

可迭代对象:
  拥有__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.延迟计算,一次返回一个结果。也就是说,它不会一次生成全部的结果,这对于大数据量处理,将会很是有用。

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

2.提升代码可读性

# 生成器和迭代器

# 迭代器 : iter next
# 能够被for循环 节省内存空间 它没有所谓的索引取值的概念 当前的值和下一个值- 公式
# 生成器和迭代器本质上是同样的
# yield函数
# 执行生成器函数 会获得一个生成器 不会执行这个函数中的代码
# 有几个yield,就能从中取出多少个值
# 生成器表达式
# 生成器表达式也会返回一个生成器 也不会直接被执行
# for循环里有几个符合条件的值生成器就返回多少值
# 每个生成器都会从头开始取值,当取到最后的时候,生成器中就没有值了
# 一个生成器只能用一次
 1 def fff():
 2     for i in range(10):
 3         yield i
 4 g2 = (i**i for i in range(20))
 5 g = fff()
 6 print(next(g))
 7 print(next(g))
 8 print(next(g))
 9 
10 print(next(fff()))
11 print(next(fff()))
12 print(next(fff()))
13 for i in fff():
14     print(i)

7、生成器相关面试题

 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))
面试题1

 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))
面试题2
 1 import os  2 
 3 def init(func):  4     def wrapper(*args,**kwargs):  5         g=func(*args,**kwargs)  6  next(g)  7         return g  8     return wrapper  9 
10 @init 11 def list_files(target): 12     while 1: 13         dir_to_search=yield
14         for top_dir,dir,files in os.walk(dir_to_search): 15             for file in files: 16  target.send(os.path.join(top_dir,file)) 17 @init 18 def opener(target): 19     while 1: 20         file=yield
21         fn=open(file) 22  target.send((file,fn)) 23 @init 24 def cat(target): 25     while 1: 26         file,fn=yield
27         for line in fn: 28  target.send((file,line)) 29 
30 @init 31 def grep(pattern,target): 32     while 1: 33         file,line=yield
34         if pattern in line: 35  target.send(file) 36 @init 37 def printer(): 38     while 1: 39         file=yield
40         if file: 41             print(file) 42 
43 g=list_files(opener(cat(grep('python',printer())))) 44 
45 g.send('/test1') 46 
47 协程应用:grep -rl /dir
tail&grep
# 一个生成器 只能取一次
# 生成器在不找它要值的时候始终不执行
# 当他执行的时候,要以执行时候的全部变量值为准
相关文章
相关标签/搜索