文章目录
函数式编程概述
- 函数式编程: 函数式=编程语言定义的函数+数学意义的函数
优点:
- 便于进行单元测试
- 便于调试
- 适合并行执行
特性
- 不可变数据(不可变:不用变量保存状态,不修改变量)
非函数式:
a=1
def incr_test1():
global a
a+=1
return a
print(incr_test1())
运行结果
2
函数式:
n=1
def incr_test2(n):
return n+1
print(incr_test2(2))
运行结果
3
- 第一类对象(函数即"变量")---------看下面高阶函数的条件
- 尾调用优化(尾递归) 尾调用:在函数最后一步调用另外一个函数(最后一行不一定是函数的最后一步)
函数bar3
在foo3
内为尾调用
def bar3(n):
return n
def foo3():
return bar3(3)
函数bar4
和bar5
在foo4
内均为尾调用,二者在if判断条件不同的情况下都有可能作为函数的最后一步
def bar4(n):
return n
def bar5(n):
return n+1
def foo4(x):
if type(x) is str:
return bar4(x)
elif type(x) is int:
return bar5(x)
函数bar6
在foo5
内为非尾调用
def bar6(n):
return n
def foo5(x):
y=bar6(x)
return y
函数bar7
在foo6
内非尾调用
def bar7(n):
return n
def foo6(x):
return bar7(x)+1
高阶函数
下面两个条件满足其中一个即可:
- 函数可以接收另一个函数作为参数
def add(x,y,f):
return f(x)+f(y)
print(add(-5,6,abs)) #f=abs abs()求绝对值
运行结果
11
- 返回值可以是函数名
def bar1():
print('from bar1')
def foo1():
print('from foo')
return bar1
m=foo1() #n=bar1
m()
运行结果
from foo
from bar1
def hanle():
print('from hanle')
return hanle
h=hanle()
h()
运行结果
from hanle
from hanle
这个就不是高阶函数
def foo(n):
print(n)
def bar(name):
print('i am name is %s'%name)
foo(bar('alex')) #bar函数没有返回值,返回值不是函数名
运行结果
i am name is alex
None
匿名函数
lambda 参数列表:函数返回值表达式语句
- 关键字
lambda
表示匿名函数 - 冒号前表示参数,冒号后表示返回值
def func(x):
return x+1
res=func(10)
print(res)
#相当于
res=lambda x:x+1
print(res(10)) #记得带参数
运行结果
11
11
res=lambda x:x+'_ab'
print(res('alex'))
运行结果
alex_ab
lambda
表达式序列
- 可以将
lambda
表达式作为序列(如列表、元组或字典等)元素,从而实现挑战表的功能,也就是函数的列表
序列=[(lambda 表达式1),(lambda 表达式2), …]
调用序列中lambda表达式的方法
序列[索引](lambda 表达式的参数列表)
定义一个lambda
表达式序列,第一个元素用于计算参数的平方,第二个元素用于计算参数的立方,第三个元素用于计算参数的四次方
arr=[(lambda x:x**2),(lambda x:x**3),(lambda x:x**4)]
print(arr[0](2),arr[1](2),arr[2](2))
运行结果
4 8 16
将lambda
表达式作为函数的返回值
def math(o):
if(o==1):
return lambda x,y:x+y
if(o==2):
return lambda x,y:x-y
if(o==3):
return lambda x,y:x*y
if(o==4):
return lambda x,y:x/y
action=math(1) #返回加法lambda表达式
print('10+2=',action(10,2))
action=math(2) #返回减法lambda表达式
print('10-2=',action(10,2))
action=math(3) #返回乘法lambda表达式
print('10*2=',action(10,2))
action=math(4) #返回除法lambda表达式
print('10/2=',action(10,2))
运行结果
10+2= 12
10-2= 8
10*2= 20
10/2= 5.0
函数嵌套
- 在某一个函数里又定义一个新函数
def father(name):
print('from father %s'%name)
def son():
print('from the son')
son()
father('alex')
运行结果
from father alex
from the son
闭包
- 融合在函数嵌套里
- 函数作用域的一种体现
python
函数式编程常用的函数(它们也是高阶函数)
map()
函数
map()
函数用于指定序列中的所有元素作为参数调用指定函数,并将结果构成一个新的序列返回map()
函数接收两个参数,一个是函数,一个是Iterable
(可迭代对象),map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回map()
作为高阶函数,事实上它把运算规则抽象了
arr=map(lambda x:x**2,[2,4,6,8,10])
for n in arr:
print(n)
运行结果
4
16
36
64
100
#enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中
arr=map(lambda x:x**2,[2,4,6,8,10])
for i in enumerate(arr):
print(i)
arr=map(lambda x,y:x+y,[1,2,3,4],[5,6,7,8]) #两个序列
for m in enumerate(arr):
print(m)
运行结果
(0, 4)
(1, 16)
(2, 36)
(3, 64)
(4, 100)
(0, 6)
(1, 8)
(2, 10)
(3, 12)
print(map(str,[1,2,3,4,5,6]))
print(list(map(str,[1,2,3,4,5,6]))) #转换成字符串
运行结果
<map object at 0x037345D0> #内存地址
['1', '2', '3', '4', '5', '6']
reduce()
函数
reduce
把一个函数作用在一个序列上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算
从python3.0
后,reduce()
函数不被集成在python
内置函数中,需要使用下面的语句引用functools
模块,才能调用reduce()
函数
from functools import reduce
def func(x,y):
return x+y
sum=reduce(func,(2,4,6,8,10))
print(sum)
运行结果
30
程序运算过程
- (1)
reduce()
函数首先使用2和4参数调用func()
函数,得到结果6 - (2)使用结果6和序列第三个元素6为参数调用
func()
函数,得到结果12 - (3)使用结果12和序列第四个元素8为参数调用
func()
函数,得到结果20 - (4)使用结果20和序列的第五个元素10为参数调用
func()
函数,得到结果30
如果要把序列[1, 3, 5, 7, 9]
变换成整数13579
,reduce
就可以派上用场
from functools import reduce
def func1(x,y):
return x*10+y
a=reduce(func1,[1,3,5,7,9])
print(a)
运行结果
13579
filter()
函数
和map()
类似,filter()
也接收一个函数和一个序列。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
在一个list
中,删掉偶数,只保留奇数
def is_odd(n):
return n%2==1
print(list(filter(is_odd,[1,2,3,4,5,6,9,10,15])))
运行结果
[1, 3, 5, 9, 15]
把一个序列中的空字符串删掉
def not_empty(s):
return s and s.strip()
print(list(filter(not_empty,['a',' ','b',None,'c',' '])))
运行结果
['a', 'b', 'c']
people=[{'name':'alex','age':100000},
{'name':'xiaoli','age':10000000},
{'name':'xiaoguo','age':9000},
{'name':'xiaowang','age':18}
]
print(list(filter(lambda p:p['age']<=18,people)))
运行结果
[{'name': 'xiaowang', 'age': 18}]
- 可见用
filter()
这个高阶函数,关键在于正确实现一个“筛选”函数 - 注意到
filter()
函数返回的是一个Iterator
,也就是一个惰性序列,所以要强迫filter()
完成计算结果,需要用list()
函数获得所有结果并返回list
map() filter() reduce()
区别
map()
处理序列中的每个元素,得到的结果是一个’列表’,该’列表’元素个数及位置与原来一样filter()
遍历序列中的每一个元素,判断每个元素得到的布尔值,如果是True
则留下来reduce()
处理一个序列,把序列进行合并操作
sorted()
函数
排序函数,排序的核心是比较两个元素的大小,如果是数字我们可以直接比较,但如果是字符串或者是字典,直接比较数学上的大小是没有意义的,因此,比较过程必须通过函数抽象出来
print(sorted([36,5,-12,9,-21]))
#sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序
print(sorted([36,5,-12,9,-21],key=abs))
print(sorted(['bob','about','Zoo','Credit']))
#默认情况下,对字符串排序,是按照ASCII的大小比较的,由于'Z' < 'a',结果,大写字母Z会排在小写字母a的前面。
#现在,我们提出排序应该忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码大加改动,只要我们能用一个key函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较
print(sorted(['bob','about','Zoo','Credit'],key=str.lower))
#反向排序
print(sorted(['bob','about','Zoo','Credit'],key=str.lower,reverse=True))
运行结果
[-21, -12, 5, 9, 36]
[5, 9, -12, -21, 36]
['Credit', 'Zoo', 'about', 'bob']
['about', 'bob', 'Credit', 'Zoo']
['Zoo', 'Credit', 'bob', 'about']
L=[('Bob',75),('Adam',92),('Bart',66),('Lisa',88)]
def by_name(t):
return t[0]
def by_score(t):
return t[1]
L2=sorted(L,key=by_name)
print('根据名字排序:',L2)
L2=sorted(L,key=by_score,reverse=True)
print('根据成绩排序:',L2)
运行结果
根据名字排序: [('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]
根据成绩排序: [('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]
zip()
函数
zip()
函数以一系列列表作为参数,将列表中对应的元素打包成一个个元组,然后返回由这些元组组成的列表
a=[1,2,3]
b=[4,5,6]
zipped=zip(a,b)
for i in zipped:
print(i)
运行结果
(1, 4)
(2, 5)
(3, 6)
如果传入的参数长度不等,则返回列表的长度和参数中长度最短的列表相同
a=[1,2,3]
b=[4,5,6,7]
zipped=zip(a,b)
for n in zipped:
print(n)
运行结果
(1, 4)
(2, 5)
(3, 6)
将打包结果前面加上操作符,并以此为参数调用zip()函数,可以将打包结果解压*
a=[1,2,3]
b=[4,5,6]
zipped=zip(a,b)
unzipped=zip(*zipped)
for m in unzipped:
print(m)
运行结果
(1, 2, 3)
(4, 5, 6)
普通函数方式与函数式编程的对比
通过学习以上的函数可以更好的看出它们的区别
以普通编程方式计算列表元素中的正数之和
list=[2,-6,11,-7,8,15,-14,-1,10,-13,18]
sum=0
for i in range(len(list)):
if list[i]>0:
sum+=list[i]
print(sum)
运行结果
64
以函数式编程方式实现
from functools import reduce
list=[2,-6,11,-7,8,15,-14,-1,10,-13,18]
s=filter(lambda x:x>0,list)
sum=reduce(lambda x,y:x+y,s)
print(sum)
运行结果
64
相比而言,函数式编程具有如下几个特点
- 代码更简单
- 数据、操作、返回值都在一起
- 没有循环体,几乎没有临时变量
- 代码用来描述要做什么,而不是怎么去做
装饰器
- 装饰器本质就是函数,为其它函数添加附加功能
原则:
- 不修改被修饰函数的源代码
- 不修改被修饰函数的调用方式
装饰器=高阶函数+函数嵌套+闭包
装饰器框架
import time
def timmer(func): #func=test1
def wrapper():
start_time=time.time()
func() #就是在运行test1()
stop_time=time.time()
print('运行时间是%s'%(stop_time-start_time))
return wrapper
@timmer #test1=timmer(test1)
def test1():
time.sleep(3)
print('test1函数运行完毕')
test1() #就是在运行wrapper
运行结果
test1函数运行完毕
运行时间是3.0667710304260254
给test1
加返回值
import time
def timmer(func): #func=test1
def wrapper():
start_time=time.time()
res=func() #就是在运行test1()
stop_time=time.time()
print('运行时间是%s'%(stop_time-start_time))
return res
return wrapper
@timmer #test1=timmer(test1)
def test1():
time.sleep(3)
print('test1函数运行完毕')
return '这是test1的返回值'
res=test1() #就是在运行wrapper
print(res)
运行结果
test1函数运行完毕
运行时间是3.0009818077087402
这是test1的返回值
加上参数
import time
def timmer(func): #func=test1
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs) #就是在运行test1()
stop_time=time.time()
print('运行时间是%s'%(stop_time-start_time))
return res
return wrapper
@timmer #test1=timmer(test1)
def test1(name,age):
time.sleep(3)
print('test1函数运行完毕,名字是【%s】 年龄是【%s】'%(name,age))
return '这是test1的返回值'
res=test1('alex',age=18) #就是在运行wrapper
print(res)
运行结果
test1函数运行完毕,名字是【alex】 年龄是【18】
运行时间是3.001091480255127
这是test1的返回值
练习题
验证功能装饰器
源代码
def index():
print('欢迎来到京东主页')
def home(name):
print('欢迎回家%s'%name)
def shopping_car(name):
print('%s的购物车里有[%s,%s,%s]' %(name,'奶茶','酸奶','苹果'))
index()
home('小李')
shopping_car('小李')
运行结果
欢迎来到京东主页
欢迎回家小李
小李的购物车里有[奶茶,酸奶,苹果]
添加一个登陆功能
def auth_func(func):
def wrapper(*args,**kwargs):
username=input('用户名:').strip()
passwd=input('密码:').strip()
if username=='小李' and passwd=='123':
func(*args,**kwargs)
else:
print('用户名或密码错误')
return wrapper
@auth_func
def index():
print('欢迎来到京东主页')
@auth_func
def home(name):
print('欢迎回家%s'%name)
@auth_func
def shopping_car(name):
print('%s的购物车里有[%s,%s,%s]' %(name,'奶茶','酸奶','苹果'))
index()
home('小李')
shopping_car('小李')
运行结果
用户名:小李
密码:123
欢迎来到京东主页
用户名:小李
密码:123
欢迎回家小李
用户名:小李
密码:123
小李的购物车里有[奶茶,酸奶,苹果]
但是这样做的话,需要每换一个页面就要登陆一次,在平时的网页中并不是这样的所以需要我们再次升级
user_dic={'username':None,'login':False} #这个说明我们现在的网页是无登录状态的
def auth_func(func):
def wrapper(*args,**kwargs):
if user_dic['username']and user_dic['login']: #如果我们有了登录名并且登录状态处于在线,就可以直接浏览网页了
res=func(*args,**kwargs)
return res
username=input('用户名:').strip()
passwd=input('密码:').strip()
if username=='小李' and passwd=='123':
user_dic['username']=username #把我们的登录名提交上去
user_dic['login']=True #登录状态处于在线
#上面这两句表明我们如果登录名和密码输入对的话,电脑就把我们的用户名提交上去,登陆状态处于在线,这样在浏览下面的网页时就不用再次登陆了
func(*args,**kwargs)
else:
print('用户名或密码错误')
return wrapper
@auth_func
def index():
print('欢迎来到京东主页')
@auth_func
def home(name):
print('欢迎回家%s'%name)
@auth_func
def shopping_car(name):
print('%s的购物车里有[%s,%s,%s]' %(name,'奶茶','酸奶','苹果'))
index()
home('小李')
shopping_car('小李')
运行结果
用户名:小李
密码:123
欢迎来到京东主页
欢迎回家小李
小李的购物车里有[奶茶,酸奶,苹果]
这样只要登陆一次就可以了
设计一个装饰器,它可作用于任何函数上,并打印该函数的执行时间
import time
def metric(fn):
start_time=time.time()
print('%s executed in %s ms'%(fn,10.24))
stop_time=time.time()
print('运行时间%s'%(stop_time-start_time))
return fn
@metric
def fast(x, y):
time.sleep(0.0012)
return x + y;
@metric
def slow(x, y, z):
time.sleep(0.1234)
return x * y * z;
f = fast(11, 22)
s = slow(11, 22, 33)
if f != 33:
print('测试失败!')
elif s != 7986:
print('测试失败!')
运行结果
<function fast at 0x02F9A078> executed in 10.24 ms
运行时间0.02792501449584961
<function slow at 0x02F9A108> executed in 10.24 ms
运行时间0.0
偏函数
Python
的functools
模块提供了很多有用的功能,其中一个就是偏函数
int()
函数可以把字符串转换为整数,当仅传入字符串时,int()
函数默认按十进制转换
print(int('12345'))
运行结果
12345
但int()
函数还提供额外的base
参数,默认值为10
。如果传入base
参数,就可以做N
进制的转换
print(int('12345',base=8)) #八进制转换
print(int('12345',16)) #十六进制转换
运行结果
5349
74565
假设要转换大量的二进制字符串,每次都传入int(x, base=2)
非常麻烦,于是,我们想到,可以定义一个int2()
的函数,默认把base=2
传进去
def int2(x,base=2):
return int(x,base)
print(int2('10000000'))
运行结果
128
functools.partial
就是帮助我们创建一个偏函数的,不需要我们自己定义int2()
,可以直接使用下面的代码创建一个新的函数int2
import functools
int2=functools.partial(int,base=2)
print(int2('10000000'))
运行结果
128
所以,简单总结functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单,注意到上面的新的int2
函数,仅仅是把base
参数重新设定默认值为2
,但也可以在函数调用时传入其他值
print(int('10000000',base=10))
运行结果
10000000