函数式编程(请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。我们首先要搞明白计算机(Computer)和计算(Compute)的概念。在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。
而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
1.高阶函数:将函数名作为参数传入(类似于函数指针的作用)。例如可以生成一个若干个函数值的列表:
#coding=utf-8 import math def lis(num,*funcs): li=[func(num) for func in funcs] return li print(lis(-5,abs,math.sin))
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
2.map/reduce:
- map:map(fcn,iterable)接受一个函数fcn和可迭代的对象iterable作为参数。可以将可迭代的对象的每一个元素通过str(函数可以是映射)映射为另一个Iterable并且返回他的可迭代Iterator。
可以直接将列表的元素转换为一个字符串的副本:list()函数可以创建一个列表
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])) ['1', '2', '3', '4', '5', '6', '7', '8', '9']
- reduce:模块functools 。reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
示例:将字符串或者是列表转换为数字
def char2num(c): return {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}[c] def num2int(d,d1): return d *10 +d1 print(functools.reduce(num2int,[1, 3, 5, 7, 9])) print(functools.reduce(num2int,map(char2num,'55663')))#可以将字符串转换为数字
示例:转换为小数
#浮点数的转换 def num2xiaoshu(d,d1): return d*0.1+d1 s='55663.3558' zheng=s[:s.index('.')]#整数部分 xiao=s[s.index('.')+1:][::-1]#小数部分 print(functools.reduce(num2int,map(char2num,zheng))+functools.reduce(num2xiaoshu,map(char2num,xiao)) /10.0)#可以将字符串转换为数字
3.筛选器:filter()函数,删除所有是函数为假的值,其余保留。
#筛选出奇数列表 def isodd(u): return u % 2==1 print(list(filter(isodd,[7,5,3,6,9])))
4.sort()的算法:
sort(obj,reverse,key):将obj排序,使用谓词key,reverse表示是否反向排序。
示例:忽略大小写,反向按照字母顺序排序:
print(sorted(['gfegfg','egrg','DGR','DGE','V'],key=str.lower,reverse=True))
5.返回函数:可以在函数内定义函数,并且可以将函数返回。如果需要函数的时候,在用调用运算符就可以调用函数了。(这个时候函数真正开始计算,这就是)
def ss(l): def sum(): ss=0 for i in l: ss+=i return ss return sum l=[2,6,5,9] print(ss(l))#<function ss.<locals>.sum at 0x000002154A51F0D0> print(ss(l)())#22
在这个例子中,我们在函数ss中又定义了函数sum,并且,内部函数sum可以引用外部函数ss的参数和局部变量,当ss返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。两次调用ss函数返回的闭包是不一样的(即使参数一样)。
r1=ss([2,6,5,9]) r2=ss([2,6,5,9]) l=[2,6,5,9] r3=ss(l) r4=ss(l) print(r1) print(r2) print(r1==r2) print(r3) print(r4) print(r3==r4) """output <function ss.<locals>.sum at 0x000001EB3EE9F0D0> <function ss.<locals>.sum at 0x000001EB3EE9F158> False <function ss.<locals>.sum at 0x000001EB3EE9F1E0> <function ss.<locals>.sum at 0x000001EB3EE9F268> False """
原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
#闭包引用外部参数的问题 def power(): lis=[] for i in range(1,4): def ii(): return i*i lis.append(ii)#添加闭包 return lis f1,f2,f3=power() print(f1(),'\n',f2(),'\n',f3())#输出9 9 9
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
def count(): def f(j): def g(): return j*j return g fs = [] for i in range(1, 4): fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f() return fs
6.匿名函数:lambda parameters:exp 可以定义一个匿名的函数。匿名函数也可以返回。(现在才知道多重赋值的数量==变量的数量)
#匿名函数 def plus(): return lambda x,y:x+y print(plus()(45,5)) #不会被引用的参数干扰 def ret(): lis=[] for i in range(1,4): lis.append(lambda j=i:j*j) return lis s1,s2,s3=ret()#多重赋值不能多 print(s1())#1 print(s2())#4 print(s3())#9
7.装饰器Decorator:__name__可以返回函数的名称。我们可以返回一个闭包获取将函数的调用“封装”到一个包中,需要的时候在传入参数并调用。
def log(func): def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper #如果加上@log 就会输出 #call plus(): #5 def plus(x,y): return x+y ff=log(plus) print(ff(2,3))
但是这样会引入多余的变量。所以使用语法糖@,@本质上等同于ff=log(plus)。所以需要在plus上加上@log表示temp=log(plus)。之后调用plus的同时可以调用log返回的闭包。解析的过程:
- 首先函数返回的是闭包wrapper给ff。
- 调用ff也就是闭包,掺入了参数2,3(参数可以任意,但是plus要求只有两个参数)
def recorder(text): def decorator(func): def wrapper1(*args): print(args) return func(args) return wrapper1 return decorator def now(*a): return True nowa = recorder('execute')(now) #第一层返回返回decorator , decorator需要一个函数作为参数。然后返回wrapper1他也要接受参数 print(nowa(*[7,8,9])) print(nowa.__name__) """ (7, 8, 9) True wrapper1 """
这么做就会导致一个问题:nowa.__name__就变为了wrapper1。所以就需要functools.wraps解决名字的不同。改为:
import functools def recorder(text): def decorator(func): @functools.wraps(func) def wrapper1(*args): print(args) return func(args) return wrapper1 return decorator def now(*a): return True nowa = recorder('execute')(now) #第一层返回返回decorator , decorator需要一个函数作为参数。然后返回wrapper1他也要接受参数 print(nowa(*[7,8,9])) print(nowa.__name__)
此时的nowa.__name__为now。wrapper1被解析到func参数上。
8.偏函数Partial function:functools.partial可以创建一个偏函数。偏函数类似于一个指定了某些特定参数的函数的“别名”,int2 = functools.partial(int, base=2)。表示int2就是可以将二进制类字节的转换为int。调用的时候也可以指定偏函数的某些参数。int2(52,base=8)。如果偏函数的其他的参数会自动加到原函数的参数上:
max2=functools.partial(max,10,20):例如max2(2,3)相当于max(10,20,2,3)相同。
整理&感谢: