文章目录
此文章参考廖雪峰大神的官网,地址:函数式编程 - 廖雪峰的官方网站 (liaoxuefeng.com)
一、什么是函数式编程
- 首先要知道的是,函数是python内建的一种封装方法,可以通过把指定段落的代码拆解成函数,通过一层一层的函数调用,从而把一个复杂的任务拆解成几个简单的任务,这种分解就叫做
面向过程的程序设计
,而函数就是面向过程编程的一个基本单元 - 那函数式编程是什么呢,从字面来看,多了一个式字,虽然也可以说是面向过程的程序设计,但是它的编程思想更接近于数学计算
-
而函数式编程就是一种抽象程度很高的编程范式,存粹的函数式编程语言编写的函数里面是没有变量的,这种函数传入参数后,因为没有变量,所以函数计算的流程是不会有变化的,导致了函数调用后输出的结果是固定的,不会有其他变化,这种函数称之为
没有副作用
-
相反的,允许在函数中使用变量的程序设计语言,由于函数内部的变量状态不确定,传入同样的参数,函数调用的输出结果可能会发生变化,因此,这种函数是
有副作用的
-
函数式编程的特点就是:允许把函数本身作为参数传入另一个函数,并且还允许调用函数后返回一个函数
注意:python对函数式编程提供了部分支持,但是由于python允许在函数中使用变量,所以python不是纯粹的函数式编程语言
二、高阶函数的概念
- 在了解了函数式编程后,我们来看高阶函数,高阶函数的英文叫
Higher-order function
,通过下面的例子,来学习高阶函数的概念
1、变量可以指向函数
-以python内置的绝对值函数abs()为例:
>>> abs(-10) #调用abs函数,成功输出结果
10
>>> abs #直接输出函数本身
<built-in function abs>
>>> a = abs(-10) #把调用函数赋值给变量,成功输出结果
>>> a
10
>>> a = abs #把函数本身赋值给变量
>>> a
<built-in function abs>
>>> a(-10) #使用赋值的变量成功调用abs函数
10
-从上面的例子可以看出:
(1)函数本身可以赋值给变量,并且赋值后的变量可以调用函数
(2)调用函数可以赋值给变量,并且赋值后变量的值为函数调用后的输出结果
2、函数名也是变量
-以abs()函数为例,可以把函数名abs看成一个变量,而这个变量指向了一个可以计算绝对值的函数
-把abs指向一个新的值:
>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
>>> abs
10
-从上面的操作可以看出,给abs重新指定值后,它不在是指向计算绝对值的函数,而是指向了新设定的整数10,所以会报错
注意:在实际环境中,内建函数等不要像上面那样随意修改,如果要恢复原本的功能,需要重启python的交互环境。并且由于abs函数实际上是定义在builtins模块中的,所以如果想要修改abs的值,使它可以在其他的模块也生效,可以使用:
import builtins
builtins.abs = 10
#这样就可以使abs的变更操作再其他模块也生效
3、传入函数
-从上面的代码行来看,变量可以指向函数,函数的参数能够接收变量,而还有一种叫做高阶函数,高阶函数的概念就是一个函数可以接收另一个函数作为参数,下面来看一个简单的高阶函数:
# -*- coding: utf-8 -*-
def add(x,y,i):
return i(x) + i(y)
print(add(-5,-6,abs))
#输出结果:
11
-上面这个高阶函数等于:
x = -5
y = -6
i = abs
i(x) + i(y) -> abs(-5) + abs(-6) -> 5 + 6 -> 11
- 看了这么多,我们可以知道,高阶函数的特点就是:
可以接收另一个函数作为参数
- 下面来看几个高阶函数:
1.map和reduce函数
- python内建了map和reduce函数,下面先简单说一下这两个函数:
- 最后引用几个小案例:
1、利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。
输入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']:
# -*- coding: utf-8 -*-
def normalize(name):
return name.title()
L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2)
#输出结果:
['Adam', 'Lisa', 'Bart']
###解析:这个直接使用title函数进行转换了,然后利用map的机制来达到效果
2、Python提供的sum()函数可以接受一个list并求和,请编写一个prod()函数,可以接受一个list并利用reduce()求积:
# -*- coding: utf-8 -*-
from functools import reduce
def prod(L):
return reduce(lambda n, m: n*m, L)
print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))
if prod([3, 5, 7, 9]) == 945:
print('测试成功!')
else:
print('测试失败!')
#输出结果:
3 * 5 * 7 * 9 = 945
测试成功!
###解析:这个和上面的乘积思路其实是一样的,只不过这里使用了lambda函数,把lambda函数作用于L,但是计算过程还是一样的:
第一次:
n = 3,m = 5
n * m = 3 * 5 = 15
第二次:
n = 15,m = 7
n * m = 15 * 7 = 105
第三次:
n = 105,m = 9
n * m = 105 * 9 = 945
3、利用map和reduce编写一个str2float函数,把字符串'123.456'转换成浮点数123.456:
# -*- coding: utf-8 -*-
from functools import reduce
def str2float(s):
return reduce(lambda x, y: x + y * pow(10, -3), map(int, s.split('.')))
print('str2float(\'123.456\') =', str2float('123.456'))
if abs(str2float('123.456') - 123.456) < 0.00001:
print('测试成功!')
else:
print('测试失败!')
#输出结果:
str2float('123.456') = 123.456
测试成功!
###解析:这个可能就比较难理解了,先看后面的用于取值的参数map函数,是先以s变量中的"."进行分割,返回一个列表,此时s = [123,456],然后同样进行依次运算,但是这次只有一次运算,因为只有两个元素:(提示:pwd(10,-3)等于10的-3次方等于0.001)
x = 123,y=456
x + y * pow(10,-3) = 123 + 456 * 0.001 = 123 + 0.456 = 123.456
2.filter函数
- filter函数同样也是python内建函数,与map函数类似
- filter函数接收两个参数,第一个函数,第二个可迭代对象,和map函数一样把传入的函数依次作用到可迭代对象的每个元素,但是和map不同的是,filter函数的返回值是根据True还是False来决定是否保留该元素的
- 下面举几个例子,看一下filter函数的具体使用:
-在一个列表中,去掉偶数,只留下奇数,通过filter可以这样写:
# -*- coding: utf-8 -*-
def fn(i):
return i % 2 == 1
print(list(filter(fn,[1,2,3,4,5,6,7,8,9])))
#输出结果:
[1, 3, 5, 7, 9]
-把一个列表中的空字符串删除:
# -*- coding: utf-8 -*-
def fn(i):
return i and i.strip()
print(list(filter(fn,["aa","cc"," "," ","vvv",None])))
#输出结果:
['aa', 'cc', 'vvv']
#其实通过上面的两种用法已经可以看出,filter函数是一个提供了“筛选”的高阶函数
#在上面的代码中,可以发现在print的时候都加了list进行转换,这也就说明filter函数的返回也是一个惰性序列
- 下面我们来使用filter函数求素数
- 引用一个案例,
- 要求:回数是指从左向右读和从右向左读都是一样的数,例如12321,909。请利用filter()筛选出回数:
# -*- coding: utf-8 -*-
def is_palindrome(n):
if n < 0 or n>0 and n%10==0:
return
n_str = str(n)
if n_str == n_str[::-1]:
return n_str
else:
return
output = filter(is_palindrome, range(1, 100))
print('1~1000:', list(output))
if list(filter(is_palindrome, range(1, 100))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99]:
print('测试成功!')
else:
print('测试失败!')
#输出结果:
1~1000: [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99]
测试成功!
#解析:
#提示:return语句用于退出函数,向调用方返回一个表达式。 return在不带参数的情况下(或者没有写return语句),默认返回None,而None是False。n_str[::-1]是倒序排列n_str的值
is_palindrome():
传入的参数先进行第一个if判断,当n小于或大于0并且除以10时等于0时,直接返回None,因为可以除以10于0的数末尾肯定是0,所以肯定不是回数,第一个if判断为false时继续往下,转换n的值为字符串类型,然后赋值给n_str,然后再进行判断,当n_str的值等于n_str倒序的值时,符合判断返回n_str的值,反之则跳出,进行下一次循环
下面利用filter函数来把is_palindrome()函数依次作用于1到99,每次循环的结果为True就把当前循环的值返回到一个惰性序列,然后print时,使用list输出output的所有元素
3.sorted函数
- 排序在程序中也是经常用到的算法,无论使用冒泡排序还是快速排序,排序的核心都是是比较两个元素的大小。
- 比较的元素如果时数字类型,那么可以直接比较,但是如果比较的元素是字符串、列表、字典呢?而python内置的高阶sorted函数就可以对列表进行比较,下面来看几个案例:
#sorted默认是从小到大排序
>>> sorted([5,2,8,6,11])
[2, 5, 6, 8, 11]
#sorted还可以接受一个key的值,例如传入key=abs,而key指定的值会依次作用于“[-5,2,8,6,-11]”的每一个元素上,然后进行排序,最终输出排序后的值
>>> sorted([-5,2,8,6,-11],key=abs)
[2, -5, 6, 8, -11]
>>> sorted([-5,2,8,6,-11])
[-11, -5, 2, 6, 8]
可以看出虽然abs函数作用于了上面列表中的每个每个元素,但是最终输出的还是列表中原有的元素,和map有点类似
#再来看一下字符串的排序
>>> sorted(["aos","oss","Pqq","Zvv"])
['Pqq', 'Zvv', 'aos', 'oss']
默认情况下python对字符串的排序,是按照ASCLL表的大小比较的,由于Z< a,所以大写字母Z会排在小写字母a的前面。
#但是利用传入参数key的值,可以忽略掉大小写进行排序
>>> sorted(["aos","oss","Pqq","Zvv"],key=str.lower)
['aos', 'oss', 'Pqq', 'Zvv']
#如果要从大到小,可以再加一个值reverse=True
>>> sorted([5,2,8,6,11],reverse=True)
[11, 8, 6, 5, 2]
-
小结:sorted()也是一个高阶函数。用sorted()排序的关键在于实现一个映射函数即,key
-
引入一个小案例:
(1)根据每个人的名字进行排序
# -*- coding: utf-8 -*-
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
return t[0]
L2 = sorted(L, key=by_name)
print(L2)
#输出结果
[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]
#解析:
因为是作用于每个值,其实也是循环,直接取下标0就可以取到名字,然后进行排序
(2)根据每个人的成绩从高到低排序
# -*- coding: utf-8 -*-
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_score(t):
return -t[1]
L2 = sorted(L, key=by_score)
print(L2)
#输出结果
[('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]
#解析:
和上面那个一样,但是因为是从高到底,所以取到的值需要加一个-,变成负数,从而到达从高到低