python基础—迭代器、生成器
python基础—迭代器、生成器
1 迭代器定义
迭代的意思是重复做一些事很多次,就像在循环中做的那样。
只要该对象可以实现__iter__方法,就可以进行迭代。
迭代对象调用__iter__方法会返回一个迭代器,所谓的迭代器就是具有next方法的对象。(在调用next方法时不需要任何参数)。在调用next方法时,迭代器会返回它的下一个值。如果next方法被调用,但迭代器没有值可以返回,就会引发一个StopITeration异常。
一个实现了__iter__方法的对象是可迭代的,一个实现了next方法的对象是迭代器。
迭代器也有__iter__方法。
2 迭代器特性
优点:
1 迭代器提供了一种不依赖索引的取值方式,这样就可以遍历那些没有索引的可迭代对象了(字典,集合,文件)
2 迭代器与列表比较,迭代器是惰性计算的,更节省内存
缺点:
1 无法获取迭代器的长度,使用不如列表索引取值灵活
2 一次性的,只能往后取值,不能倒着取值
迭代规则的关键是?为什么不是使用列表?
1 如果有一个函数,可以一个接一个地计算值,那么在使用时可能是计算一个值时获取一个值,而不是像列表一样获取所有的值。如果有很多值,列表会占用太多的内存。
2 使用迭代器更通用,更简单,更优雅
3 迭代器代码
(1)索引方式循环
1
2
3
4
5
6
7
|
l = [ 'a' , 'b' , 'c' , 'd' ] i = 0 while i< len (l): print (l[i]) i + = 1 for i in range ( len (l)): print (l[i]) |
(2)只要被内置了__iter__方法就是可迭代对象
可迭代的:只要对象本身有__iter__方法,那他就是可迭代的
1
2
3
4
|
l = [ 'a' , 'b' , 'c' , 'd' ] l.__iter__() #相当于iter(l) iter (l) i = iter (l) |
i 就是迭代器,i 就是列表l的迭代器
(3)迭代器的__next__方法
迭代器使用__next__方法,可以不依赖下标取值,超出字典长度会有结束报错。
调用__next__方法必须是迭代器,也只有迭代器才有__next__方法。
1
2
3
4
5
6
7
|
l = [ 'a' , 'b' , 'c' , 'd' ] i = iter (l) print (i.__next__()) print (i.__next__()) print (i.__next__()) print (i.__next__()) print (i.__next__()) |
输出结果:
1
2
3
4
5
|
a b c d StopIteration #报错 |
(4)while循环与迭代器
1
2
3
4
5
6
7
|
d = { 'a' : 1 , 'b' : 2 , 'c' : 3 , 'd' : 4 } i = iter (d) while True : try : print ( next (i)) except StopIteration: break |
输出结果:
1
2
3
4
|
b c a d |
(5)for循环与迭代器
在这里for就是使用可迭代对象的__iter__方法返回此可迭代对象的迭代器,并且执行此迭代器的next方法,并且出现StopITeration报错就结束
For循环与列表
1
2
3
|
d = { 'a' : 1 , 'b' : 2 , 'c' : 3 , 'd' : 4 } for i in d: print (i) |
For循环与文件
1
2
3
|
with open ( 'a.txt' , 'r' ) as f: for line in f: print (line.strip()) |
(6)迭代器也有__iter__方法
f 的迭代器也有__iter__方法,并且指向f这个迭代器本身
(7)迭代器是一次性的
只能往后取值,不能倒着取值
可迭代对象不是一次性的。
1
2
3
4
5
6
7
8
|
d = { 'a' : 1 , 'b' : 2 , 'c' : 3 , 'd' : 4 } i = iter (d) print ( next (i)) print ( next (i)) print ( next (i)) print ( next (i)) for x in i: #迭代器i已经将上面的值取完,for循环在这里没有值可取 print (x) |
输出结果:
1
2
3
4
|
d a b c |
(8)isinstance判断对象是否是可迭代对象和迭代器对象
Isinstance使用例子:
1
2
|
print ( type ( 'ssssss' ) is str ) print ( isinstance ( "ssssss" , str )) |
输出结果:
1
2
|
True True |
用isinstance判断是不是可迭代对象与迭代器对象
#查看可迭代对象和迭代器对象
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
|
from collections import Iterable,Iterator str1 = 'hello' list1 = [ 1 , 2 , 3 ] tuple1 = ( 1 , 2 , 3 ) dict1 = { "a" : 1 , "b" : 2 , "c" : 3 } set1 = { 1 , 2 , 3 , 4 } f1 = open ( 'a.txt' ) #都是可迭代的 str1.__iter__() list1.__iter__() tuple1.__iter__() dict1.__iter__() set1.__iter__() f1.__iter__() print ( isinstance (str1,Iterable)) #True print ( isinstance (list1,Iterable)) #True print ( isinstance (tuple1,Iterable)) #True print ( isinstance (dict1,Iterable)) #True print ( isinstance (set1,Iterable)) #True print ( isinstance (f1,Iterable)) #True #查看是否是迭代器 print ( isinstance (str1,Iterator)) #False print ( isinstance (list1,Iterator)) #Flase print ( isinstance (tuple1,Iterator)) #Flase print ( isinstance (dict1,Iterator)) #Flase print ( isinstance (set1,Iterator)) #Flase print ( isinstance (f1,Iterator)) #Flase |
4、生成器
4.1 生成器函数定义
生成器就是一个函数,这个函数内包含有yield这个关键字
返回一个生成器对象。生成器本质就是迭代器
生成器是一个包含yield关键字的函数。当它被调用时,在函数体中的代码不会被执行,而会返回一个迭代器。每次请求一个值,就会请求生成器中的代码,直到遇到一个yield或者return语句。Yield语句意味着生成一个值。Return语句意味着生成器要停止执行。
生成器是由两部分组成的:生成器的函数和生成器的迭代器。生成器的函数是有def语句定义的,包含yield部分。生成器的迭代器是这个函数返回的部分。生成器函数返回的迭代器可以像其他迭代器那样使用。
4.2 yield作用
生成器与return有何区别?
Return只能返回一次函数就彻底结束了,而yield返回多次值
yield到底干了什么事情?
1 yield把函数变成生成器-,生成器实质就是迭代器。 相当于把__iter__和__next__方法封装到函数内部。
2 用return返回值能返回一次,而yield返回多次
3 函数在暂停以及继续下一次运行时的状态是由yield保存
任何包含yield语句的函数称为生成器。Yield不是像return那样返回值,而是每次产生多个值。生成器每次产生一个值(使用yield语句),函数就会被冻结:即函数停在那点等待被重新唤醒。函数被重新唤醒之后就从停止的那点开始。
4.3 生成器运行详解
Test函数中yield返回给g一个生成器,实质就是迭代器,通过next方法取值,并且在取值时会执行test函数和取yield的返回值。即next函数会触发生成器函数g的运行,遇到yield停止
1
2
3
4
5
6
7
8
9
|
from collections import Iterable,Iterator def test(): print ( "first" ) yield 1 g = test() print (g) print ( next (g)) print ( isinstance (g,Iterable)) print ( isinstance (g,Iterator)) |
输出结果:
1
2
3
4
5
|
<generator object test at 0x000000E8CBDE5360 > first 1 True True |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def test(): print ( "one" ) yield 1 print ( "two" ) yield 2 print ( "three" ) yield 3 g = test() res = next (g) print (res) res = next (g) print (res) res = next (g) print (res) res = next (g) print (res) |
输出结果:
1
2
3
4
5
6
7
|
one 1 two 2 three 3 StopIteration #报错 |
1
2
3
4
5
6
7
8
9
10
|
def test(): print ( "one" ) yield 1 print ( "two" ) yield 2 print ( "three" ) yield 3 g = test() for i in g: print (i) |
输出结果:
1
2
3
4
5
6
|
one 1 two 2 three 3 |
一个yield可以返回多个值,接收的类型是元组
yield可以返会任何类型的值
1
2
3
4
|
def test(): yield 1 , 2 , 3 , 4 g = test() print ( next (g)) |
输出结果:
1
|
( 1 , 2 , 3 , 4 ) |
生成器与return有何区别?
Return只能返回一次函数就彻底结束了,而yield返回多次值
yield到底干了什么事情?
1 yield把函数变成生成器--->迭代器
2 用return返回值能返回一次,而yield返回多次
3 函数在暂停以及继续下一次运行时的状态是由yield保存
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
def countdown(n): print ( "start countnum" ) while n> 0 : yield n n - = 1 print ( 'done' ) g = countdown( 5 ) print ( next (g)) print ( next (g)) print ( next (g)) print ( next (g)) print ( next (g)) try : print ( next (g)) #这一次next函数应用没有yield返回,会报错StopIteration,需要try检测 except StopIteration: pass g = countdown( 5 ) print ( next (g)) for i in g: #for会将g变成迭代器:iter(g),执行next取值,并捕捉next出现的异常 print (i) |
输出结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
start countnum 5 4 3 2 1 done start countnum 5 4 3 2 1 done |
4.4 用生成器模拟管道
tailf命令的实现
1 普通函数实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#/usr/bin/env python import time def tail(file_path): with open (file_path, 'r' ) as f: f.seek( 0 , 2 ) while True : line = f.readline() if not line: time.sleep( 0.3 ) continue else : print (line,end = "") tail( "/tmp/a.txt" ) |
2 生成器函数实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#/usr/bin/env python import time def tail(file_path): with open (file_path, 'r' ) as f: f.seek( 0 , 2 ) while True : line = f.readline() if not line: time.sleep( 0.3 ) continue else : yield line g = tail( "/tmp/a.txt" ) print (g) for line in g: print (line) |
tail -f |grep “error” 实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#/usr/bin/env python import time #定义阶段:定义两个生成器函数 def tail(file_path): with open (file_path, 'r' ) as f: f.seek( 0 , 2 ) while True : line = f.readline() if not line: time.sleep( 0.3 ) continue else : yield line def grep(pattern,lines): for line in lines: if pattern in line: yield line #调用阶段:得到两生成器对象 g = tail( "/tmp/a.txt" ) gg = grep( "error" ,g) #next触发执行gg生成器函数 for line in gg: print (line) |
1 迭代器的应用
文件名:a.txt 文件内容如下:
apple 10 3
tesla 100000 1
mac 3000 2
lenovo 30000 3
chicken 10 3
实现功能:cat a.txt|grep apple
要求1:定义迭代器函数cat
要求2:定义迭代器函数grep
要求3:模拟管道的功能,将cat的处理结果作为grep的输入
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def cat(file_auth): with open (file_auth, "r" ) as f: f.seek( 0 ) for line in f: yield line def grep(pattern,lines): for line in lines: if pattern in line: yield line cat_func = cat( "/tmp/a.txt" ) grep_func = grep( "apple" ,cat_func) for line in grep_func: print (line) |
5、协程函数
5.1 协程函数定义
如果在一个函数内部yield的使用方式是表达式形式的话,如x=yield,那么该函数成为协程函数。
yield是表达式方式使用,则必须用send方法进行传值。
协程函数=生成器+yield表达式+send方法
5.2 e.send与next(e)的区别
e.send与next(e)的区别
1 如果函数内yield是表达式形式,那么必须先next(e)
使用send方法只有在生成器挂起之后才有意义(也就是在yield函数第一次被执行之后),因此要用send方式时需要用next方法初始化生成器。
用next(e)初始化 相当于 e.send(None) ,可以用装饰器解决初始化问题
2 二者的共同之处是都可以让函数在上次暂停的位置继续运行,不一样的地方在于send在触发下一次代码的执行时,会顺便给yield传一个值。
end可以传多个值,但是必须是元组类型
5.3协程函数举例
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def eater(name): print ( "%s start to eat food" % name) while True : food = yield print ( "%s get %s,to start eat" % (name,food)) print ( "done" ) e = eater( "people" ) print (e) next (e) e.send( "apple" ) e.send( "fruit" ) e.send( "beef" ) e.send( "meet" ) |
输出结果:
1
2
3
4
5
6
|
<generator object eater at 0x000000F944583D00 > people start to eat food people get apple,to start eat people get fruit,to start eat people get beef,to start eat people get meet,to start eat |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def eater(name): print ( "%s start to eat food" % name) food_list = [] while True : food = yield food_list print ( "%s get %s,to start eat" % (name,food)) food_list.append(food) print ( "done" ) e = eater( "people" ) print (e) next (e) print (e.send( "apple" )) print (e.send( "fruit" )) print (e.send( "beef" )) print (e.send( "meet" )) |
输出结果:
1
2
3
4
5
6
7
8
9
10
|
<generator object eater at 0x0000006F92D83D00 > people start to eat food people get apple,to start eat [ 'apple' ] people get fruit,to start eat [ 'apple' , 'fruit' ] people get beef,to start eat [ 'apple' , 'fruit' , 'beef' ] people get meet,to start eat [ 'apple' , 'fruit' , 'beef' , 'meet' ] |
2 生成器的应用
把下述函数改成生成器的形式,执行生成器函数的到一个生成器g,
然后g.send(url),打印页面的内容,利用g可以无限send
1
2
3
4
5
6
7
8
9
10
11
12
|
from urllib.request import urlopen def get(url): while True : def index(): print (url) print (urlopen(url).read()) url = yield index g = get( "http://www.baidu.com" ) next (g) baidu = g.send( "http://www.baidu.com" ) baidu() |
5.4 协程函数用装饰器初始化
协程函数使用装饰器完成初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
def auth(func): def wrapper( * args, * * kwargs): res = func( * args, * * kwargs) next (res) return res return wrapper @auth def eater(name): print ( "%s start to eat food" % name) food_list = [] while True : food = yield food_list print ( "%s get %s,to start eat" % (name,food)) food_list.append(food) print ( "done" ) e = eater( "people" ) e.send( "123" ) |