Python函数式编程以及高阶函数

Posted 礁之

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python函数式编程以及高阶函数相关的知识,希望对你有一定的参考价值。

文章目录


此文章参考廖雪峰大神的官网,地址:函数式编程 - 廖雪峰的官方网站 (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 Python函数式编程以及高阶函数

从延迟处理讲起,JavaScript 也能惰性编程?

现代C++函数式编程

函数式编程 & Python中的高阶函数map reduce filter 和sorted

复习4

函数式编程:容器类型值类型