Python学习笔记四(迭代器、生成器、内置函数)
一、迭代器
1.迭代器定义
迭代是一个重复的过程,每次重复一次迭代,并且每次迭代的结果都是下一次迭代的初始值。
l = ["aaa","bbb","ccc"]
count = 0
while count< len(l): #每次重复完成后count都是下一次的初始值
print(l[count])
count+=1
需要迭代器的原因:对于序列类型str、list、tuple可以依赖索引迭代取值,对于dict、set、文件需要提供不依赖索引取值的方式。
可迭代对象:具有内置__iter__方法的对象。字符串、列表、元组、字典、集合和文件均为可迭代对象。
迭代器对象:可迭代对象执行obj.__iter__()方法后得到的结果为迭代器对象。迭代器对象内置有__iter__和__next__方法。文件对象为迭代器对象。
总结:
1.可迭代对象不一定是迭代器对象。
2.迭代器对象一定是可迭代对象。
3.调用obj.__iter__()方法,可得到迭代器对象,若本身为迭代器对象,执行该方法得到的仍然是它本身。
2.迭代器的使用
可迭代对象调用obj.__iter__()方法得到迭代器对象后,可使用obj.__next__()迭代取值,迭代器对象可直接调用obj.__next__()取值。
obj.__next__()等同于next(obj),取完值后调用__next__会抛出StopIteration异常。
#每次调用__next__方法取一次值,取完之后再调用该方法会抛出StopIteration异常
l = ["aaa","bbb","ccc"]
iter_list = l.__iter__()
print(iter_list.__next__())
print(iter_list.__next__())
print(iter_list.__next__())
print(iter_list.__next__())
使用try...except捕获异常
d = {"a":212,"b":111,"c":222}
iter_dict = d.__iter__()
while True:
try:
print(next(iter_dict)) #字典迭代取到的为key
except StopIteration:
break
for循环原理
for k in obj
a)调用in后的obj__iter=obj.__iter__()得到一个迭代器对象。
b) 执行k=obj_iter.__next__()将取到的值赋给k,然后执行循环体。
c)重复过程2,直到捕获到StopInteration异常,结束循环。
3.迭代器的优缺点
优点:
a) 提供一种统一的、不依赖索引的取值方式,为for循环的实现提供了依据。
b) 迭代器同一时间在内存中只有一个值,更节省内存。
缺点:
a) 只能往后取值,为一次性的。
b) next执行完之前,不能统计值得个数,无获取长度。
二、生成器
1.生成器定义
只要函数内部包含有yield关键字,那么执行func()的到的就是生成器,不会执行函数内部代码,并且生成器就是迭代器。
def func():
print("1111111111")
yield 1
print("2222222222")
yield 2
print("3333333333")
yield 3
g = func() #g为生成器,生成器就是迭代器
print(next(g)) #func()开始执行到第一个yield,并且打印yield返回值
print(next(g)) #func()执行到第二个yield,并且打印yield返回值
print(next(g))
yield的功能:
a) yield提供了一种自定义迭代器的方法
b) yield于return的区别:yield可以返回多次值,return只能返回一次值。函数暂停与再继续的状态有yield保存。
模拟管道,实现tail -f access.log | grep "404"
1 #tail -f access.log|grep "404"
2 import time
3 def tail(filepath):
4 with open(filepath,'rb') as f:
5 f.seek(0,2)
6 while True:
7 line = f.readline()
8 if line:
9 yield line
10 else:
11 time.sleep(0.05)
12
13 def grep(lines,pattern):
14 for line in lines: #调用tail后得到生成器对象,可使用for来迭代其中每一行内容
15 line =line.decode('UTF-8')
16 if pattern in line:
17 yield line
18
19 res = grep(tail("access.log"),"404") #res也为生成器对象,可使用for来迭代取值
20 for line in res:
21 print(line)
2.协程函数
表达式形式的yield,在使用时,第一次必须穿None,g.send(None)等同于next(g)
1 def eater(name):
2 print("%s开始吃了"%name)
3 food_list = []
4 while True:
5 food = yield food_list
6 print("%s吃了%s"%(name,food))
7 food_list.append(food)
8
9 g = eater("xxx") #创建生成器g
10 res1 = g.send(None) #初始化yield,拿到返回的空列表
11 print(res1)
12 res2 = g.send("米饭") #发送"米饭"给yield,继续执行代码到下一个yield,返回列表给res2
13 print(res1)
14 g.close() #结束迭代
15 res3 = g.send("面条") #无法发送和获取值
16 print(res3)
实现功能:grep -rl 'python' /etc
1 #从某个路径下搜索所有包含某个字符串的文件的路径
2 import os
3
4 def init(func):
5 def inner(*args,**kwargs):
6 res = func(*args,**kwargs)
7 next(res)
8 return res
9 return inner
10
11 #列出某个路径下所有文件的绝对路径
12 @init
13 def list_path(target):
14 while True:
15 file_path = yield
16 g = os.walk(file_path)
17 for pardir,_,files in g:
18 for file in files:
19 abs_path = "%s\%s"%(pardir,file)
20 target.send(abs_path)
21
22 #打开某个文件
23 @init
24 def openner(target):
25 while True:
26 abs_path = yield
27 with open(abs_path,"rb") as f:
28 target.send((abs_path,f))
29
30 #读取文件中每一行
31 @init
32 def read_line(target):
33 while True:
34 abs_path,f = yield
35 for line in f:
36 flag = target.send((abs_path,line))
37 if flag:
38 break
39
40 #从一行中查找某个字符
41 @init
42 def find_str(target,pattern):
43 flag = False #用于判断某个文件是否包含有重复的某个字符
44 pattern = pattern.encode("utf-8")
45 while True:
46 abs_path,line = yield flag
47 if pattern in line:
48 target.send(abs_path)
49 flag = True
50
51 #将结果打印出来
52 @init
53 def print_path():
54 while True:
55 abs_path = yield
56 print(abs_path)
57
58 g = list_path(openner(read_line(find_str(print_path(),"abcdef"))))
59 g.send(r"D:\PycharmProjects")
3.yield总结
a) 可以把函数做成迭代器
b) 对比return,可以返回多次值,可以挂起/保存函数的运行状态
三、面向过程编程
面向过程是一种思路和思想,不依赖于具体的语言或语法,核心思想是过程二字,即按照流水线式分步骤解决问题。
优点是复杂问题流程化,分解为简单的小功能。
缺点是可扩展性差,修改流水线任一阶段,会影响到其他阶段。
适合扩展性要求不高的场景,入linux内核、git、httpd等。
四、三元表达式、列表推导式、生成器表达式
1.三元表达式
name = input(">>:").strip()
res = "SB" if name=="xxx" else "NB" #满足if条件,则返回if前的值,不满足则返回else后的值
print(res)
2、列表推导式
l = [str(i)+"xxx" for i in range(1,10)] #为1-10所有数字添加后缀
print(l)
l = [str(i)+"xxx" for i in range(1,20) if i%2==0] #1-20所有偶数加后缀
print(l)
3、生成器表达式
将列表推导式的[]换为()就是生成器表达式。
#生成老母鸡,需要使用时调用next下蛋
>>> chicken = ("鸡蛋%s"%i for i in range(1,10))
>>> chicken
<generator object <genexpr> at 0x000001AFD64CDF68>
>>> next(chicken)
'鸡蛋1'
>>> list(chicken) #第一个鸡蛋已经下过了,chicken可迭代,因此可转为列表
['鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
4、声明式编程
a) 将l=['xxx','abc_sb','aaa','bbb']中的字母全部变大写
l=['xxx','abc_sb','aaa','bbb']
l = [i.upper()for i in l]
print(l)
b) 将l=['xxx','abc_sb','aaa','bbb']中以sb结尾的名字过滤掉,然后保存剩下的名字长度
l=['xxx','abc_sb','aaa','bbb']
l = [len(i) for i in l if not i.endswith("sb")]
print(l)
c) 求文件test.txt中最长的行的长度(长度按字符个数算,需要使用max函数)
with open("test.txt",'r',encoding="utf-8") as f:
res = max(len(line) for line in f)
print(res)
d) 求文件test.txt中总共包含的字符个数?思考为何在第一次之后的n次sum求和得到的结果为0?(需要使用sum函数)
with open("test.txt",'r',encoding="utf-8") as f:
res = sum(len(line) for line in f)
print(res)
生成器只能往后取值,已经取完一次值后,再对生成器做求和结果为0
五、递归和二分法
1.递归调用定义
在调用一个函数的过程中,直接或间接调用了该函数本身,称之为递归调用。
递归调用分为两个阶段:递推和回溯。
python中使用sys.gettrcursionlimit()查看可递归调用的层数。
import sys
print(sys.getrecursionlimit()) #默认支持1000层递归调用
l=[1,[2,[3,[4,[5,[6,[7,]]]]]]]
def func(l):
for item in l:
if type(item) is list:
func(item)
else:
print(item)
func(l)
2.python中递归调用特点
python中递归调用效率低,需要在进入下一次递归时保留当前状态。其他语言中有尾递归优化,python中没有,并且对层级做了限制。
递归使用的要求:
a) 必须有一个明确的结束条件
b) 每次进入更深一层递归时,问题规模比上一次有所减少
c) 递归效率不高,层数过多易导致栈溢出
3.二分法
1 l = [2,3,5,6,8,12,15,16,19,22,35,45,56,62,98,122,321]
2 def binary_search(l,num):
3 print(l)
4 mid_index = len(l)//2
5 if len(l) != 0:
6 if num < l[mid_index]:
7 binary_search(l[0:mid_index-1],num)
8 elif num > l[mid_index]:
9 binary_search(l[mid_index+1:],num)
10 else:
11 print("find it")
12 else:
13 print("can't find %s" % num)
14 binary_search(l,321)
六、匿名函数
匿名函数一次性使用,随时随需定义,可应用在max、min、sorted、map、reduce、filter等函数中。
#找出薪水最高的人
salaries = {
'abc':3222,
'def':111,
'aaa':431,
'xxx':4133
}
#普通方法
g = zip(salaries.values(),salaries.keys()) #g为迭代器
print(max(g)) #max按照每次取值的第一个数来比较
#定义函数的方法,打印出薪水最高的人的名字
def func(k):
return salaries[k]
print(max(salaries,key=func)) #排序是按照key来排序,结果展示按照salaries默认结果展示
#使用匿名函数
print(max(salaries,key=lambda k:salaries[k]))
七、内置函数
sorted
#薪水排序,输出人名
salaries = {
'abc':3222,
'def':111,
'aaa':431,
'xxx':4133
}
print(sorted(salaries,key=lambda k:salaries[k]))
print(sorted(salaries,key=lambda k:salaries[k],reverse=True))
map
#给列表中所有元素批量添加后缀
#普通方法
names = ['xxx','aaa','eee']
l =[]
for name in names:
res = "%s_SB"%name
l.append(res)
print(l)
#使用map添加
g = map(lambda name:'%s_SB'%name,names)
print(g) #g为迭代器
print(list(g))
filter
#筛选出某个序列中特定元素
names = ["xxx_sb","aaa_sb","sss","ee_sb"]
g = filter(lambda i:i.endswith("sb"),names)
print(g)
print(list(g))
reduce
#筛选出某个序列中特定元素
#某个序列中连续两个元素进行处理
from functools import reduce
print(reduce(lambda x,y:x+y,range(1,101),100)) #最后的100为初始值,可以不添加。