没有学不会的python--函数式编程以及高阶函数
没有学不会的python
函数式编程
到现在为止,我们的没有学不会python系列文章已经讲了很多知识点了,如果是第一次刷到这篇文章的朋友可以去我主页看一下以前写的文章。前面讲了很多知识点,每个知识点都辅以代码作为理解和练手,这些例子都是用面向过程思想编写的,面向过程的本质就是将一个个复杂问题拆解成简单问题,意思就是将一个以实现某目的的程序划分成一个个函数的意思,这就是面向过程的编程思想。
而函数式编程,虽然也是面向过程的一种编程思想,但它又跟一般的面向过程写法有些不一样,简单说就是它更抽象更简洁,而带来的副作用就是,代码比较难懂,执行效率也相对低一点。因此面向对象编程思想可分为两种,一种是函数式编程另外一种是命令式编程,它们的区别是:
函数式编程关心数据的映射,命令式编程关心解决问题的步骤。翻译成人话就是,函数式编程只负责处理数据的加工运算,不管代码逻辑结构,是一种更加抽象的方法。命令式编程就关注于代码逻辑结构,有时为了使代码结构更合理更易懂,我们不介意用非常臃肿的写法去完成一个运算目的。
python作为一门高级语言,自然也支持函数式编程,因为在python中一切皆是对象。可能有些朋友不知道对象是什么意思,前面讲过的那些变量就是对象,对象就是一个个在内存中拥有地址的变量。而函数,也是一种变量,在python中,由于函数也可以作为变量进行传递,所以python也支持函数式编程。
函数也是一个对象
要怎么理解函数也是一个对象呢,最好的办法就是通过代码认识一下。
上面我们首先定义了一个函数str_to_int,目的是用于将字符类型的数字转换成整型,然后定义一个求和函数,该函数接受一个字符串转换成整型的函数参数以及一个待求和的参数列表。最后我们定义一个带求和的列表,接着调用求和函数,但是我们仔细看一下,调用get_totoal函数的时候,我们传递了一个str_to_int对象,这个变量其实就是我们前面声明的函数,它也可以和一般变量一样作为参数传递给其它函数。当然这个例子没什么实际用处,实际也不会这么写,这里只是为了让大家明白,在python中,记住只是在python中,在其他语言不一定,函数也是一个对象,也是可以作为参数进行传递的。
知道了函数也是一个对象,可作为参数传递后,我们学习一下匿名函数。
lambda函数
匿名函数意思就是没有名字的函数,作为初学者可能会很奇怪,没有名字的函数?那怎么调用这个函数,不是没有名字么?接下来会为你解答。匿名函数由于没有名字,因此也不适用于涉及一大段的代码块,所以它又有另外一个说法,叫做一句话函数,就是说一条语句就可以写完的函数。
匿名函数的一般形式是:
lambda param1:expression
即lambda关键词+变量名称+冒号+表达式。表达式将会对参数进行运算并返回结果。
看个例子:
通过上面的例子我们知道怎么定义和调用匿名函数了,也应该明白,我们不应该让匿名函数做过多的事情,如果需要做过多的事情就要定义成普通函数。匿名函数适合那种一句话就可以完成的函数,这类函数定义成普通函数会过于臃肿,而定义成匿名函数则恰到好处。
接着解释一下匿名函数怎么调用的,上面的匿名函数是直接用括号括起来然后调用的,这样对于初学者来说不太好理解,我再这么写个例子就应该明白了:
# 定义并调用一个匿名函数,该函数将对参数进行+1并将结果返回my_func = lambda x: x + 1print(my_func(5))
这里我们把匿名函数赋值给一个变量,然后通过这个变量来调用匿名函数。就和普通函数一样,可以通过函数名称调用函数并传递值。
匿名函数真正的威力是和其它几个python的高阶函数结合使用,它们分别是map,reduce,filter,sorted。
map函数
map函数一般形式是:
其中func是一个函数变量,可以是普通函数也可以是匿名函数,seq是可迭代对象。关于什么是可迭代对象本章节不会讲,以后会有专门的章节讲,现在我们可以把可迭代对象当成list,dict这些复合类型就行。
map的实际使用:
# map调用普通函数def str_to_int(str_obj): return int(str_obj)result = map(str_to_int, ['1', '2', '3'])print(list(result))# map调用匿名函数result = map(lambda x: int(x), ['1', '2', '3'])print(list(result))# 可以传递多个参数result = map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])print(list(result))
输出结果:
要注意的是,map函数会将参数的每一项都拿出来传入给函数对象,而不是将参数作为一个整体传入。并且map返回的是一个惰性的迭代对象,所以如果我们要拿返回结果,应该用list计算一下。
另外,传递多个参数和一个参数的计算方式是不同的,为了方便理解,请看下图:
一个参数时,计算方式是这样的:
即将参数的每一项循环计算输出。
两个参数时,计算方式是这样的:
即将对应参数的对应项循环传递进行计算,比如上图的就是x=[1,2,3]、y=[4,5,6],接着循环对应项进行计算,先取第一项就是x[0]+y[0]就是1+4,如此循环。
reduce函数
reduce函数的一般形式和map一样也是:
reduce(func,seq)
不过值得注意的是,reduce在python3中被移植到了functools模块中,所以我们要先引入才能使用。reduce的计算思想比较难以用语言描述,不过reduce翻译成中文的意思就是合并。我们可以简单的理解为合并运算。下面通过代码来理解一下:
输出结果:
6I Love you
上述代码实现了两个功能,分别是求和和链接字符串。求和的计算过程翻译成图就是:
第一次先将列表的前两项传入给x,y,然后计算和返回,再接着取列表的下一项跟返回的和继续传入给x,y接着求和,如此循环。
filter函数
filter函数的形式跟map也是一样的,这里不再重复。这个函数比较好理解,它就是把符合条件的值都过滤出来并返回。
比如下面的代码就是将大于5的值都过滤出来返回:
输出结果:
[7, 8, 9]
我们可以用这个函数来做一些简单的过滤操作。
sorted函数
sorted是python内置的一个排序函数,使用它,就不需要我们去写排序算法了,而且它的使用非常灵活。废话不多说,直接看例子:
输出结果:
[1, 10, 23, 34][34, 23, 10, 1][('one', 1), ('two', 2), ('four', 4)][('four', 4), ('one', 1), ('two', 2)]
sorted函数的更多用法就需要你们自己取摸索,对于初学者来说,这些就够了。
函数式编程的利与弊
函数式编程有好的一面就有坏的一面,不是什么情况都适合用函数式编程的,况且python并不完全支持函数式编程的所有特性。关于函数式编程的利与弊,我结合前面的例子做一个综合的说明。假如我们要实现一个功能,需要先对数字字符进行转换为整型然后再求和,求和之后,如果结果比10大就返回,比10小就丢弃。这么一个功能如果用函数式来写的话就是:
这个函数给你第一眼感觉是什么?晕是吧。别说初学者,就是有经验的人,看到这样的写法都头疼,虽然很简短的代码就实现了这么多功能,但是代码阅读性太差了,即使是本人,过段时间回过头来,也未必知道自己写的是什么玩意。
如果我把它这样写呢:
def str_to_int(str_obj): return int(str_obj)def get_total(list_1): total = 0 for item in list_1: total += str_to_int(item) return totalresult_1 = get_total(['1', '2', '3'])result_2 = get_total(['4', '5', '6'])result = [result_1, result_2]print(list(filter(lambda x: x > 10, result)))
这样代码是不是好很多,虽然代码量多了,但是可阅读性高了,你好我好大家好。
我们在选择函数式编程的时候,切记不要混合使用,要易易懂为主,炫酷为次。python的函数式编程本质上是为了简短的普通函数服务的,并不适合功能复杂的函数。而且有些公司明确规定不然程序员写函数式编程,怕的就是以后你的代码没人看懂,不知道怎么修改。
好了,今天就到这里。
如果对我的文章感兴趣,可以关注我的主页也可以关注我的公众号: