一、生成器补充
1.什么是生成器?
可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他的数据类型需要调用自己内置的__iter__方法),所以生成器就是可迭代对象。
2.生成器分类
(1)生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行。
(2)生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表。
3.为何使用生成器之生成器的优点
Python使用生成器对延迟操作提供了支持。
所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。
4.生成器函数
def lay_eggs(num):
for egg in range(num):
res='蛋%s' %egg
yield res
print('下完一个蛋')
laomuji=lay_eggs(10)
print(laomuji) #打印生成器在内存中的地址
print(laomuji.__next__()) #执行生成器函数
print(laomuji.__next__())
print(laomuji.__next__())
egg_l=list(laomuji)
print(egg_l)
运行结果:
<generator object lay_eggs at 0x0000000000A320F8>
蛋0
下完一个蛋
蛋1
下完一个蛋
蛋2
下完一个蛋
下完一个蛋
下完一个蛋
下完一个蛋
下完一个蛋
下完一个蛋
下完一个蛋
下完一个蛋
['蛋3', '蛋4', '蛋5', '蛋6', '蛋7', '蛋8', '蛋9']
5.生成器表达式和列表解析
egg_list=['鸡蛋%s' %i for i in range(10)] #列表解析
laomuji=('鸡蛋%s' %i for i in range(10))#生成器表达式
print(laomuji)
print(next(laomuji)) #next本质就是调用__next__
print(laomuji.__next__())
print(next(laomuji))
运行结果:
<generator object <genexpr> at 0x0000000000D820A0>
鸡蛋0
鸡蛋1
鸡蛋2
总结:
(1)把列表解析的[]换成()得到的就是生成器表达式
(2)列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
(3)Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。
例如,sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:
sum(x ** 2 for x in xrange(4))
6.注意
人口信息.txt文件的内容
{'name':'北京','population':10}
{'name':'南京','population':100000}
{'name':'山东','population':10000}
{'name':'山西','population':19999}
def get_provice_population(filename):
with open(filename) as f:
for line in f:
p=eval(line) #将字符串转换成字典
yield p['population']
gen=get_provice_population('人口信息')
all_population=sum(gen)
print(all_population)
for p in gen:
print(p/all_population)
运行结果:
130009
为什么只有1个运行结果呢?
执行上面这段代码,将不会有任何输出,这是因为,生成器只能遍历一次。在我们执行sum语句的时候,就遍历了我们的生成器,当我们再次遍历我们的生成器的时候,将不会有任何记录。所以,上面的代码不会有任何输出。
示例:
def test():
for i in range(4):
yield i
g=test()
g1=(i for i in g)
g2=(i for i in g1)
print(list(g1))
print(list(g2))
运行结果:
[0, 1, 2, 3]
[]
7.协程函数
(1)协程函数就是使用了yield表达式形式的生成器
def eater(name):
print("%s eat food" %name)
while True:
food = yield
print("done")
g = eater("gangdan")
print(g)
<generator object eater at 0x00000000011B80A0>
(2)协程函数赋值过程
要先运行next(),让函数初始化并停在yield,相当于初始化函数,然后再send(),send会给yield传一个值
next()和send()都是让函数在上次暂停的位置继续运行,
next是让函数初始化
send在触发下一次代码的执行时,会给yield赋值
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)
e=eater('钢蛋')
print(e)
print(next(e)) # 现在是运行函数,让函数初始化
print(e.send('包子')) #
print(e.send('韭菜馅包子'))
print(e.send('大蒜包子'))
运行结果:
<generator object eater at 0x00000000011C8150>
钢蛋 start to eat food
[]
钢蛋 get 包子 ,to start eat
['包子']
钢蛋 get 韭菜馅包子 ,to start eat
['包子', '韭菜馅包子']
钢蛋 get 大蒜包子 ,to start eat
['包子', '韭菜馅包子', '大蒜包子']
用装饰器修饰协程函数
def deco(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
next(res)
return res
return wrapper
@deco #g=deco(g)
def eater(name):
print('%s ready to eat' %name)
food_list=[]
while True:
food=yield food_list
food_list.append(food)
print('%s start to eat %s' %(name,food))
g=eater('alex') #wrapper('alex')
print(g)
next(g) #等同于 g.send(None)
g.send('手指头')
g.send('脚指头')
g.send('别人的手指头')
g.send('别人的脚指头')
print(g)
print(g.send('脚趾头1'))
print(g.send('脚趾头2'))
print(g.send('脚趾头3'))
运行结果:
alex ready to eat
<generator object eater at 0x0000000000A12258>
alex start to eat None
alex start to eat 手指头
alex start to eat 脚指头
alex start to eat 别人的手指头
alex start to eat 别人的脚指头
<generator object eater at 0x0000000000A12258>
alex start to eat 脚趾头1
[None, '手指头', '脚指头', '别人的手指头', '别人的脚指头', '脚趾头1']
alex start to eat 脚趾头2
[None, '手指头', '脚指头', '别人的手指头', '别人的脚指头', '脚趾头1', '脚趾头2']
alex start to eat 脚趾头3
[None, '手指头', '脚指头', '别人的手指头', '别人的脚指头', '脚趾头1', '脚趾头2', '脚趾头3']
应用场景
grep -rl 'python' /root
import os
def send_none(func):
'''
装饰器函数:初始化表达式yield的生成器函数(send一个None)
'''
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
res.send(None) # next(res)
return res
return wrapper
@send_none
def get_abs_path(open_file):
'''
获取目录下所有文件的绝对路径函数,并send给open_file
'''
while True:
walk_path = yield
w = os.walk(walk_path)
for path,_,files in w:
for file in files:
file_abs_path = r'%s\%s' % (path, file)
open_file.send(file_abs_path) # 将得到的绝对路径通过send方式传给open_file这个生成器函数
@send_none
def open_file(match_line):
'''
获得文件句柄函数,并将文件句柄和文件路径以元祖的形式send给match_line
'''
while True:
file_abs_path = yield
with open(file_abs_path,encoding='utf-8') as f:
match_line.send((file_abs_path,f)) # send可以将多个参数打包成元祖传递给生成器函数
@send_none
def match_line(word):
'''
遍历通过yield获取到的文件里的每一行,找到关键字是否存在于改行,存在关键字的文件,打印输出
(使用标签位位,避免重复打印)
'''
while True:
file_abs_path,f = yield
Flag = False # 定义一个标志位
for line in f:
if Flag: # 如果标志位被更改为True则跳出循环(为了防止一个文件有多行关键字,print时存在重复)
break
if word in line :
Flag = True # 当匹配到关键字所在的行,将标志位改为Ture
print(file_abs_path)
walk_path = r'E:s17day05a'
g = get_abs_path(open_file(match_line('python'))) # 获得get_abs_path()生成器对象
g.send(walk_path)
运行结果:
E:s17day05aa1
E:s17day05acc1
E:s17day05acdd1
总结:
面向过程的程序设计:是一种流水线式的编程思路,是机械式
优点:
程序的结构清晰,可以把复杂的问题简单
缺点:
扩展性差
应用场景:
linux内核,git,httpd
二、递归
1.递归调用
在函数调用过程中,直接或间接地调用了函数本身,这就是函数的递归调用。
def f1():
print('f1')
f2()
def f2():
f1()
f1()
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
使用以下方式查询:
import sys
print(sys.getrecursionlimit())
运行结果:
1000
2.尾递归
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化。
3.应用场景:二分法
逻辑是将列表取中间值做比较,然后取左边或右边再比较
l = [1, 2, 10,33,53,71,73,75,77,85,101,201,202,999,11111]
def search(find_num,seq):
if len(seq) == 0:
print('not exists')
return
mid_index=len(seq)//2
mid_num=seq[mid_index]
print(seq,mid_num)
if find_num > mid_num:
#in the right
seq=seq[mid_index+1:]
search(find_num,seq)
elif find_num < mid_num:
#in the left
seq=seq[:mid_index]
search(find_num,seq)
else:
print('find it')
search(77,l)
search(72,l)
search(-100000,l)
运行结果:
[1, 2, 10, 33, 53, 71, 73, 75, 77, 85, 101, 201, 202, 999, 11111] 75
[77, 85, 101, 201, 202, 999, 11111] 201
[77, 85, 101] 85
[77] 77
find it
[1, 2, 10, 33, 53, 71, 73, 75, 77, 85, 101, 201, 202, 999, 11111] 75
[1, 2, 10, 33, 53, 71, 73] 33
[53, 71, 73] 71
[73] 73
not exists
[1, 2, 10, 33, 53, 71, 73, 75, 77, 85, 101, 201, 202, 999, 11111] 75
[1, 2, 10, 33, 53, 71, 73] 33
[1, 2, 10] 2
[1] 1
not exists
三、匿名函数
1.lambda
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。
f=lambda x,y:x+y
print(f)
print(f(1,2))
运行结果:
<function <lambda> at 0x0000000000B40268>
3
map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
运行结果:
[1, 4, 9, 16, 25, 36, 49, 64, 81]
匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
2.lambda用法
(1)把匿名函数赋值给一个变量,再利用变量来调用该函数
f = lambda x: x * x
print(f)
f(5)
运行结果:
<function <lambda> at 0x10453d7d0>
25
(2)匿名函数作为返回值返回
def build(x, y):
return lambda: x * x + y * y
t=build(4,3)
print(t())
(3)max,min,zip,sorted的用法
salaries={
'egon':3000,
'alex':100000000,
'wupeiqi':10000,
'yuanhao':2000
}
print(max(salaries)) #只根据字典的key进行比较
res=zip(salaries.values(),salaries.keys()) #接收任意个参数,返回一个元组列表
print(res)
print(list(res))
#匿名函数用法
print(max(salaries,key=lambda k:salaries[k]))
print(min(salaries,key=lambda k:salaries[k]))
print(sorted(salaries)) #默认的排序结果是从小到到
print(sorted(salaries,key=lambda x:salaries[x])) #默认的排序结果是从小到大
print(sorted(salaries,key=lambda x:salaries[x],reverse=True)) #默认的排序结果是从小到大
运行结果:
yuanhao
<zip object at 0x0000000000A17A88>
[(3000, 'egon'), (100000000, 'alex'), (2000, 'yuanhao'), (10000, 'wupeiqi')]
alex
yuanhao
['alex', 'egon', 'wupeiqi', 'yuanhao']
['yuanhao', 'egon', 'wupeiqi', 'alex']
['alex', 'wupeiqi', 'egon', 'yuanhao']
3.global用法
如果你想要为一个定义在函数外的变量赋值,那么你就得告诉Python这个变量名不是局部的,而是全局的。
我们使用global语句完成这一功能。没有global语句,是不可能为定义在函数外的变量赋值的。
注意:应该尽量避免这样做,因为这使得程序的读者会不清楚这个变量是在哪里定义的。
x=1000
def f1():
x=0 #只在局部生效
f1()
print(x) #打印的是全局变量x
运行结果:
1000
x=1000
def f1():
global x #调用全局变量x
x=0
f1()
print(x)
运行结果:
0
4.内置函数
- map函数
Map接受一个方法和一个集合作为参数。它创建一个新的空集合,以每一个集合中的元素作为参数调用这个传入的方法,然后把返回值插入到新创建的集合中。
l=['alex','wupeiqi','yuanhao']
res=map(lambda x:x+'_SB',l)
print(res)
print(list(res))
运行结果:
<map object at 0x00000000007FB9B0>
['alex_SB', 'wupeiqi_SB', 'yuanhao_SB']
nums=(2,4,9,10)
res1=map(lambda x:x**2,nums)
print(list(res1))
运行结果:
[4, 16, 81, 100]
- reduce函数
reduce()传入的函数必须接收两个参数,reduce()对list的每个元素反复调用函数,并返回最终结果值。
from functools import reduce
l=[1,2,3,4,5]
print(reduce(lambda x,y:x+y,l,10)) #reduce(f,[1,2,3,4],10) #1+2+3+4+10
运行结果:
25
- filter函数
对每个元素进行判断,返回True或False,filter()根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新list。
l=['alex_SB','wupeiqi_SB','yuanhao_SB','egon']
res=filter(lambda x:x.endswith('SB'),l)
print(res)
print(list(res))
运行结果:
<filter object at 0x000000000108BA58>
['alex_SB', 'wupeiqi_SB', 'yuanhao_SB']
四、面向过程编程与函数编程
1.概念
函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
而函数式编程(请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。
我们首先要搞明白计算机(Computer)和计算(Compute)的概念。
在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。
而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。
对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
2.总结
面向过程解释:
函数的参数传入,是函数吃进去的食物,而函数return的返回值,是函数拉出来的结果,面向过程的思路就是,把程序的执行当做一串首尾相连的函数,一个函数吃,拉出的东西给另外一个函数吃,另外一个函数吃了再继续拉给下一个函数吃。。。
例如:
用户登录流程:前端接收处理用户请求-》将用户信息传给逻辑层,逻辑词处理用户信息-》将用户信息写入数据库
验证用户登录流程:数据库查询/处理用户信息-》交给逻辑层,逻辑层处理用户信息-》用户信息交给前端,前端显示用户信息
3.高阶函数
- map函数应用
array=[1,3,4,71,2]
ret=[]
for i in array:
ret.append(i**2)
print(ret)
#如果我们有一万个列表,那么你只能把上面的逻辑定义成函数
def map_test(array):
ret=[]
for i in array:
ret.append(i**2)
return ret
print(map_test(array))
#如果我们的需求变了,不是把列表中每个元素都平方,还有加1,减一,那么可以这样
def add_num(x):
return x+1
def map_test(func,array):
ret=[]
for i in array:
ret.append(func(i))
return ret
print(map_test(add_num,array))
#可以使用匿名函数
print(map_test(lambda x:x-1,array))
#上面就是map函数的功能,map得到的结果是可迭代对象
print(map(lambda x:x-1,range(5)))
运行结果:
[1, 9, 16, 5041, 4]
[1, 9, 16, 5041, 4]
[2, 4, 5, 72, 3]
[0, 2, 3, 70, 1]
<map object at 0x0000000001503A20>
- reduce函数应用
from functools import reduce
#合并,得一个合并的结果
array_test=[1,2,3,4,5,6,7]
array=range(100)
#报错啊,res没有指定初始值
def reduce_test(func,array):
l=list(array)
for i in l:
res=func(res,i)
return res
# print(reduce_test(lambda x,y:x+y,array))
#可以从列表左边弹出第一个值
def reduce_test(func,array):
l=list(array)
res=l.pop(0)
for i in l:
res=func(res,i)
return res
print(reduce_test(lambda x,y:x+y,array))
#我们应该支持用户自己传入初始值
def reduce_test(func,array,init=None):
l=list(array)
if init is None:
res=l.pop(0)
else:
res=init
for i in l:
res=func(res,i)
return res
# print(reduce_test(lambda x,y:x+y,array))
print(reduce_test(lambda x,y:x+y,array,50))
运行结果:
4950
5000
- filter函数应用
movie_people=['alex','wupeiqi','yuanhao','sb_alex','sb_wupeiqi','sb_yuanhao']
def tell_sb(x):
return x.startswith('sb')
def filter_test(func,array):
ret=[]
for i in array:
if func(i):
ret.append(i)
return ret
print(filter_test(tell_sb,movie_people))
#函数filter,返回可迭代对象
print(filter(lambda x:x.startswith('sb'),movie_people))
运行结果:
['sb_alex', 'sb_wupeiqi', 'sb_yuanhao']
<filter object at 0x0000000001487C18>