Python(4)函数式编程以及高阶函数


此文章参考廖雪峰大神的官网,地址:函数式编程 - 廖雪峰的官方网站 (liaoxuefeng.com)

一、什么是函数式编程

  • 首先要知道的是,函数是python内建的一种封装方法,可以通过把指定段落的代码拆解成函数,通过一层一层的函数调用,从而把一个复杂的任务拆解成几个简单的任务,这种分解就叫做面向过程的程序设计,而函数就是面向过程编程的一个基本单元
  • 那函数式编程是什么呢,从字面来看,多了一个式字,虽然也可以说是面向过程的程序设计,但是它的编程思想更接近于数学计算

看了上面两段话之后,可能会有一点懵,没有关系,我们现在先来看一下计算机和计算的概念:

  • 计算机:对于计算机来说,CPU是计算机的大脑,负责执行代码以及各种指令,所以汇编语言是最贴近计算机的语言,汇编语言再往下就是二进制了
  • 计算:计算指的是数学意义上的计算,而越是抽象的计算,距离计算机的硬件越远,就像是计算机硬件在第一层,而数学计算在第五层

现在来看编程语言,对应上面说的一层和五层的关系,也就是说:

  • 越低级的编程语言(越贴近计算机硬件),层数低(抽象程度低),下楼下的越快(执行效率越高),例如C语言

  • 相反的,越是高级的编程语言(离计算机硬件越远),层数高(抽象程度高),下楼下的越慢(执行效率越低),例如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函数,下面先简单说一下这两个函数:
  • map():

map函数接收两个参数,第一个是函数,第二个是可迭代(Iterable)对象,map函数会将传入的函数,依次作用到迭代器的每一个元素,并且返回一个惰性序列(Iterator)。下面来举几个例子,帮助更好的理解map函数:

-假设有一个函数和列表:
def f(x):
    return x * x 
L = list(range(11))   

-现在想让f函数依次的作用到L列表的每一个元素上,该怎么做,传统的写法:

# -*- coding: utf-8 -*-
def f(x):
    return x * x 
L = list(range(11))   

new_L = []
for i in L:
    new_L.append(f(i))
print(new_L)

#输出结果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

-这样虽然可以达到效果,但是感觉还是比较繁琐,而使用map就可以这样写:

# -*- coding: utf-8 -*-
def f(x):
    return x * x 
L = list(range(11))   

print(list(map(f,L)))   

#输出结果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

-可以看到最终的效果是相同的,但是使用map明显比传统的写法更快捷,而在print时使用list进行转换,是因为上面说过,map的返回是一个惰性序列,所以需要使用list转换一下
  • reduce():

现在再来看reduce函数,reduce把一个函数作用在一个序列上,这个序列类似于[a1,a2,a3,a4...]这样的,并且reduce函数必须接收两个参数,在作运算时,reduce函数会把结果和序列的下一个元素做累计运算,下面来看一个例子:

-有一个函数和列表:
def add(x,y):
    return x + y
L = [1,2,3,4,5]

-现在想要利用add函数,把L列表的元素依次相加,类似于1+2+3这样的,使用reduce可以这样写:

# -*- coding: utf-8 -*-
from functools import reduce
def add(x,y):
    return x + y
L = [1,2,3,4,5]
print(reduce(add,L))

#输出结果:
15

-对于reduce函数,我的理解是,reduce使用了add函数,来把L列表的元素依次相加,运算结果为:

第一次计算:
x = 1,y = 2
x + y = 1 + 2 = 3
第二次计算:
x = 3,y = 3
x + y = 3 + 3 = 6
第三次计算:
x = 6,y = 4 
x + y = 6 + 4 = 10
第四次计算:
x = 10,y = 5
x + y = 10 + 5 = 15

-可以看到,y的值是从下标1开始递增的(下标0的值是x),而x的值为每次相加后的值,如果变成乘积呢:

# -*- coding: utf-8 -*-
from functools import reduce
def add(x,y):
    return x * y
L = [1,2,3,4,5]
print(reduce(add,L))

#输出结果:
120

-同样,计算结果为:

第一次计算:
x = 1,y = 2
 x * y = 1 * 2 = 2
第二次计算:
x = 2,y = 3
 x * y = 2 * 3 = 6
第三次计算:
x = 6,y = 4 
 x * y = 6 * 4 = 24
第四次计算:
x = 24,y = 5
 x * y = 24 * 5 = 120

-列表元素相加可以使用sum函数,现在又想把[1,2,3,4,5]变为整数12345,这种情况也可以使用reduce

# -*- coding: utf-8 -*-
from functools import reduce
def add(x,y):
    return x * 10 + y
L = [1,2,3,4,5]
print(reduce(add,L)) 

#输出结果:
12345

-计算过程都是大同小异的
第一次计算:
x = 1,y = 2
x * 10 + y = 1 * 10 + 2 = 12
第二次计算:
x = 12,y = 3
x * 10 + y= 12 * 10 + 3 = 123
第三次计算:
x = 123,y = 4 
x * 10 + y= 123 * 10 + 4 = 1234
第四次计算:
x = 1234,y = 5
x * 10 + y = 1234 * 10 + 5 = 12345

-继续上面的,如果想要把字符串类型转为整数类型,例如'12345'转为12345,想实现这样的效果可以配合map函数:
# -*- coding: utf-8 -*-
from functools import reduce
def add(x,y):
    return x * 10 + y

def str_num(i):
    dic = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
    return dic[i]

print(reduce(add,map(str_num,'12345')))

#输出结果:
12345

-上面的代码,利用map使用str_num依次执行,取出字符串对应的整数数值,然后使用reduce来把整数数值转换为最终的效果
-简化后,变成:
# -*- coding: utf-8 -*-
from functools import reduce
dic = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def str_int(i):
    def fn(x,y):
        return x * 10 + y
    def str_num(i):
        return dic[i]
    return reduce(fn,map(str_num,i))
print(str_int('12345'))

#输出结果:
12345

-其实就是把两个函数压缩成了一个函数
-拓展——————————利用lambda函数还可以进一步简化为:
# -*- coding: utf-8 -*-
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2num(s):
    return DIGITS[s]
def str2int(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))
print(str2int('12345'))    

#输出结果:
12345
  • 最后引用几个小案例:
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、利用mapreduce编写一个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函数求素数
  • 计算素数的方法:

计算素数的一个方法是埃氏筛法,它的算法理解起来非常简单:

首先,列出从2开始的所有自然数,构造一个序列:

2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …

取序列的第一个数2,它一定是素数,然后用2把序列的2的倍数筛掉:

3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …

取新序列的第一个数3,它一定是素数,然后用3把序列的3的倍数筛掉:

5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …

取新序列的第一个数5,然后用5把序列的5的倍数筛掉:

7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …

不断筛下去,就可以得到所有的素数。

  • 下面利用filter来取到小于100的所有素数:
# -*- coding: utf-8 -*-
def fn():
    n = 1
    while True:
        n = n + 2 
        yield n 

def if_num(n):
    return lambda x: x % n > 0

def primes():
    yield 2
    it = fn()
    while True:
        n = next(it)
        yield n 
        it = filter(if_num(n),it) 

for n in primes():
    if n < 100:
        print(n)
    else:
        break
        
#输出结果:
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97

#解析:
1、首先循环(调用)主函数“primes()”,遇到了”yield 2“终止,回到for循环进行判断2<100,所以先返回2,然后把fn函数赋值给it。

2、而”fn()“,的第一个值是3while循环每次都加2,相当于把2的倍数去除,得到了素数3,接着遇到yield终止,并返回了3for循环,返回for循环后进行判断,3小于100,输出3,然后从上次终止的地方开始继续运行。

3、把n带入为“if_num(n),然后使用filter函数进行判断,已知if_num函数返回的结果”lambda x:x % n > 0,此时n=3,所以”lambda x:x % 3 > 0“因为使用了filter函数,所以it的元素会依次作为x传入”lambda x:x % 3 > 0“这个判断式,依次判断,为True则把相应的元素返回到一个惰性序列,相当于把3的倍数去除了,这个时候因为while循环,所以又到了n = next(it),而此时it序列的所有3的倍数的值已经去除,再加上本来已经过滤掉了2的倍数的值,而42的倍数,被去除了,所以下一个值是5,然后遇到yield终止,回到for循环进行判断,5<100所以输出54、继续刚才的步骤,”lambda x:x % 5 > 0“,同样it序列的所有元素会依次赋值给x,然后得到一个没有5倍数的序列,回到while的第一步,此时已经去掉了235的倍数,63的倍数,被去除了,所以下一个是7,重复上面的步骤,去除掉7的倍数,此时去掉了2357的倍数,82的倍数,93的倍数,105的倍数,都被去除了,所以下一个值直接到了11,依次推类,得到了小于100的所有素数
  • 引用一个案例,
- 要求:回数是指从左向右读和从右向左读都是一样的数,例如12321909。请利用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,因为可以除以100的数末尾肯定是0,所以肯定不是回数,第一个if判断为false时继续往下,转换n的值为字符串类型,然后赋值给n_str,然后再进行判断,当n_str的值等于n_str倒序的值时,符合判断返回n_str的值,反之则跳出,进行下一次循环

下面利用filter函数来把is_palindrome()函数依次作用于199,每次循环的结果为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)]

#解析:
和上面那个一样,但是因为是从高到底,所以取到的值需要加一个-,变成负数,从而到达从高到低
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值