岁旱连夏秋,客玦厌尘土。
欣然成一笑,爱此清夜雨。
——陆游《雨夕》
本期文章4970字
根据之前文章的后台统计数据推算
本期预计所需阅读时间30分钟
本系列文章已加入“维权骑士”(rightknights.com)的版权保护计划
本文原创内容受到保护
函数式编程总述
顾名思义,函数式编程是一种基于函数的编程风格。
函数式编程的关键部分是高阶函数(higher-order functions),我们在之前的内容中已经使用过这种“把功能分割成一个一个函数”的编程思想。
高阶函数是一种将其他函数作为参数,或将它们作为返回结果的函数。来看例子:
defapply_twice(func, arg):
return func(func(arg))
defadd_five(x):
return x + 5
print(apply_twice(add_five, 10))
运行结果:
可以看到,在最后的print函数中,函数apply_twice把另一个函数add_five作为其参数,并在其函数体内调用了两次apply_twice函数。
之前我们已经了解到,函数可以作为参数直接被另一个函数使用(详见第7篇文章中的内容:“函数有时像变量一样”),所以上面例子中这种直接把add_five作为apply_twice的参数的写法是没有问题的。
测试题13.1.
代码的输出结果是?
def test(func, arg):
return func(func(arg))
def mult(x):
return x * x
print(test(mult, 2))
点击下方空白区域查看答案
▼
16
很容易看出来,函数mult()的作用是把传入的参数做平方并返回。在题中,函数test()对传入的数值2调用了两次mult(),第一次返回2*2==4,第二次返回4*4==16,所以最后返回到print函数进行输出的值为16。
可是有人要问了,为什么要像上面这样写程序呢,直接把所有功能写到一个函数里不好吗?这就涉及到“函数式编程”的具体思想。
函数式编程的重点在于:在程序中用到的所有函数都是纯函数。纯函数的概念是,这种函数只做自己分内的事,并且返回的值仅依赖于传入的参数。
比如在上面的测试题13.1.的代码中,函数test()的作用只有一个:“对参数arg执行两次func()函数”,而不管那些不在它分内的其他事情(比如arg具体是几,或者func()函数具体做了什么)。函数mult()的作用也只有一个:把传给我的那个参数做平方,返回去,不管传进来的参数是几,它都会对参数取平方。像这样的参数就是纯函数。
这其实很好理解,类似于数学中函数的工作原理:例如,函数cos(x)+1只管把参数x取一次余弦值再加1。在这个函数中我们看不出余弦值具体是怎么取出来的,而且同样的x值算出的cos(x)+1值是相同的。这就是纯函数:这个函数只管它自己分内的事,返回的值也只与传入的参数有关,与其他事情无关。
以下是纯函数和不纯函数的示例。
纯函数:
defpure_function(x, y):
temp = x + 2*y
return temp / (2*x + y)
不纯函数:
some_list = []
defimpure(arg):
some_list.append(arg)
可以看到,上面这个不纯函数使用了它分外的东西:它改变了some_list数组。
测试题13.2.
下面这个函数是纯函数吗?
def func(x):
y = x**2
z = x + y
return z
点击下方空白区域查看答案
▼
不一定,要看具体情况
如果变量y和z在函数以外没有定义过,在函数体内是新定义的,那么函数func()就是纯函数。如果变量y和z在函数以外定义过,那么对于y和z的重新赋值就属于对函数外的元素进行了修改,这样函数func()就不是纯函数了。
在代码中使用纯函数,既有优点也有缺点。
纯函数方便了我们在写代码过程中的思考和测试。由于每个函数的功能都很纯粹,在我们的思路中,它们的功能就很明了,并且在测试程序的时候,如果哪个功能有了问题,我们可以直接找到实现此功能的函数,而不必在一个不纯函数内部所实现的许许多多个功能中去找我们测试出错的功能。这就是函数的目的——孤立每一个功能,既可以使开发过程变得清晰,也可以使使用的功能成为一个一个单独的整体。
同时,把每一个单独的功能分别写成小的纯函数后,如果我们需要基于之前实现了的功能去开发更高级的功能,我们就不必从头开始,而是可以直接把之前的基础功能纯函数拿来调用,就可以为更高级功能的实现打好基础,不必把基础功能重写一遍。这说明,在程序中使用纯函数,增强了程序的可扩展性(可以类比之前提到过的Python扩展模块来理解这一部分,扩展模块在一定意义上就是一个个独立的基础功能)。
另外,纯函数在运行过程中更高效。打个比方,如果我们现在需要对一系列数据分别计算cos(x)+1和cos(x)+2的值,我们如果把这两种计算分别写成两个完全没有关系的不纯函数,那么每一个x值的余弦值就会被计算两次。而如果我们写一个专门用来计算余弦值的函数,再分别调用这个函数去+1和+2,也可以实现所需的功能,但是由于是在同一次运行过程内,所以第一次已经计算过一次的余弦值会留在内存中,等到第二次再调用计算余弦值的函数计算同一个x值的余弦值时,计算机意识到这个x值在之前调用过余弦值函数,就会直接去找内存中计算完成的值,这样就会节省一次计算时间,在工作量比较大时程序运行效率可以大幅提高,正所谓牺牲缓存空间(存储之前运行过程中各函数的各次返回值)换来了运行速度。
还有,纯函数使程序更容易并行运行。并行运行的意思是,我们可以同时多次执行同样的功能——还是用上面的例子来说,我们需要计算cos(x)+1和cos(x)+2的值,如果在同一个程序中分别把它们写成两个独立的不纯函数,那么程序就必须分别计算两个函数的返回值,由于没有其他事情可做,导致程序在每一个时间点只能做一次运算。反观用纯函数写出来的程序,程序每计算出来一个cos(x)值后,我们可以把程序设计成同时计算这个cos值的+1和+2之后的值,这样就可以同时得到两个结果。在计算机支持的前提下(比如部署了多核处理器),并行计算的效率比单线计算的效率有了成倍的提升(多核处理器就是这个原理。以前的单核处理器,不管核心运行速度有多快,每次只能执行一个任务。而多核处理器可以在操作系统的分配下,让每个核心独立处理一项任务,使单位时间的处理能力成倍提升)。
当然,完全使用纯函数来写程序也有缺点,仅使用纯函数的主要缺点在于它们使一些简单任务变得非常复杂,因为我们需要把每个小功能都拆成单独的函数。在某些情况下,由于纯函数过于细碎,他们之间的调用关系也可能更难理清楚。
lambda函数
lambda在代码中看似是个表达式,其实是个函数。只不过,lambda函数是没有函数名的,我们把这种没有函数名的函数称为匿名函数。
由于是我们接触到的第一个匿名函数,所以关于lambda函数的介绍不是很容易开始。我们先看一个lambda函数:
lambda x, y: x*y
这个函数的输入参数是x和y,返回值是它们的积x*y。
再看一个例子:
lambda:1
这个函数没有输入参数,它的返回值是1。
从上面的两个例子可以看到,lambda函数的一般格式为:
lambda参数列表:表达式
上面的参数列表不需要写括号,多个参数之间用逗号分隔。
lambda函数的语法是固定的,其本质上只有上面这一种用法——定义一个lambda函数。在实际中,根据lambda函数应用场景的不同,可以将lambda函数的用法扩展为以下几种:
1. 将lambda函数直接赋给一个变量(注意,不是把返回值赋值给变量,而是把lambda函数本身赋给变量),通过这个变量来间接调用那个lambda函数。来看例子:
add = lambda x, y: x+y
可以看到,上面例子中的lambda函数是一个计算加法的函数。在例子中我们将其赋给变量add,这样变量add便成为了具有计算加法的功能的函数。例如,在上例基础上,如果我们运行
add(1,2)
返回值为3。
2. 将lambda函数赋给其他函数,从而将目标函数的功能用该lambda函数的功能替换。
例如,在标准库time中,函数sleep的功能是使程序按参数指定的秒数来休眠一段时间。如果我们想要屏蔽此功能,我们可以在程序的一开始写这样的语句:
time.sleep= lambda x:None
这样,在后续代码中调用time库的sleep函数,将不会执行原有的功能。例如,在上例基础上,执行
time.sleep(3)
时,程序不会休眠3秒钟,而是什么都不做。
来看一个lambda函数的用法举例:
defmy_func(f, arg):
return f(arg)
print( my_func(lambdax: 2*x*x, 5) )
运行结果:
50
lambda函数没有普通的那种用def语句写出来的有名字的函数那么强大。
lambda函数只能实现那种一个表达式就能实现的功能,而这种功能通常就相当于一行代码。相比较来说,def语句定义出来的函数,只要缩进正确,可以在函数体写很多很多代码。来看例子:
# 有名字的函数:
defpolynomial(x):
return x**2 + 5*x + 4
print(polynomial(-4))
#lambda函数:
print((lambdax: x**2 + 5*x + 4) (-4))
运行结果:
在上面这个例子中,我们把x**2 + 5*x + 4作为了lambda函数的函数功能,并向这个lambda函数传递了参数-4。
测试题13.3.
填空,创建一个lambda函数,这个函数的返回值是其参数值的平方。同时,以8作为参数调用这个lambda函数。
a = ( _____ x _____ x*x) ( _____ )
领取专属 10元无门槛券
私享最新 技术干货