(Python3) – 函数式编程

函数式编程(请注意多了一个“式”字)——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返回的闭包。解析的过程:

  1. 首先函数返回的是闭包wrapper给ff。
  2. 调用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)相同。

整理&感谢: