Python学习笔记3.1.1 函数式编程(Functional Programming)之高阶函数(map/reduce)

概念:

函数是python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解程简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。

而函数式编程(请注意多了一个”式“字)--Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。

我们首先要搞明白计算机(Computer)和计算(Compute)的概念。

在计算机的层次上,CPU执行的式加减乘除的指令代码,以及各种条件判断和跳转指令,所以汇编语言是最贴近计算机的语言。

而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。

对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!1

Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

高阶函数(Higher-order function):

高阶函数英文叫Higher-order function。什么是高阶函数?我们以实际代码为例子,一步一步深入概念。

变量可以指向函数

以Python内置的求绝对值的函数abs()为例,调用函数可以使用

abs(-10)

   如果只写abs呢?

abs

    可见,abs(-10)是函数调用,而abs是函数本身。

要获得函数调用结果,我们可以把结果赋值给变量:

x = abs(-10)

       如果把函数本身赋值给变量呢?

f = abs

结论:函数本身也可以赋值给变量,即:变量可以指向函数

如果一个变量指向了一个函数,那么,可否通过该变量来调用这个函数?

答案是可以的,说明变量f可以指向abs函数本身。直接调用abs()函数和调用变量f()完全相同。

回归到标题:变量可以指向函数

函数名也是变量

那么函数名是什么呢?函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!

abs = 10
print(abs(-10))
Traceback (most recent call last):
  File "e:\py\Higher-order function.py", line 9, in <module>
    print(abs(-10))
          ^^^^^^^^
TypeError: 'int' object is not callable

把abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10。

当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,请重启Python交互环境。

这里也就回归到标题:函数名也是变量。

传入函数

既然变量可以指向函数,函数的参量能接受变量,那么一个函数就可以接受另一个函数作为参数,这种函数就称之为高阶函数(high-order function)。

一个最简单的高阶函数:

def add(x,y,f)
    return f(x)+f(y)

当我们调用add(-5,6,abs)时,参数x,y和f分别接收-5,6和abs,根据函数定义,我们可以推导计算过程为:

x = -5 \\ y = 6 \\ f =abs \\ f(x)+f(y) ==> abs(-5) +abs(-6) ==> 11 \\ return \space 11

用代码验证:

print(add(-5,6,abs))

标题传入函数的意思就是把一个函数作为参数传入到一个函数中,也即高阶函数。

map/reduce

python内建了map()和reduce()函数

此处,廖雪峰老师给了一篇引文:MapReduce:大规模集群上简化数据处理(Simplified Data Processing on Large Clusters)

map:map()函数接收两个参数,一个是函数,一个是Iterable(这个前文中有提到,即可用于for循环的一系列数据类型),map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

举例说明,比如我们有一个函数f(x)=x^2,要把这个函数作用在一个list[1,2,3,4,5,6,7,8,9]上,就可以用map()实现如下:

用python代码实现:

def f(x):
    return x*x
r = map(f,[1,2,3,4,5,6,7,8,9])
print(list(r))

map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列

(注:惰性序列(Lazy Sequence):也称为延迟求值序列,是一种在需要时才计算其元素的序列。与普通序列(如列表)不同,惰性序列不会一次性将所有元素都计算出来,而是等到你真正需要某个元素时,才会去计算它。

特点:

  • 延迟计算:只有在真正需要时才计算元素的值。
  • 无限序列:可以表示无限长的序列,因为不需要一次性存储所有元素。
  • 内存效率:对于大型数据集,惰性序列可以显著减少内存占用。
  • 灵活组合:惰性序列可以方便地与其他操作组合,如过滤、映射等。

应用场景:

  • 数据流处理:处理大规模数据集时,惰性序列可以逐个处理元素,避免内存溢出、
  • 函数式编程:惰性序列是函数式编程中的重要概念,可以实现许多强大的编程模式
  • 生成器(generator):Python中的生成器就是一种创建惰性序列的常见方式
  • 无限序列:惰性序列可以表示无限序列,如自然数序列,斐波那契数列等)

因此通过list()函数让它把整个序列都计算出来并返回一个list。

你可能会想,不需要map()函数,写一个循环,也可以计算出结果:

L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
    L.append(f(n))
print(L)

的确可以,但是,从上面的循环代码,能一眼看明白”把f(x)作用在list的每一个元素并把结果生成一个新的list“吗?

所以,map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x^2,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:

print(list(map(str,[1,2,3,4,5,6,7,8,9])))

只需要一行代码

reduce:reduce把一个函数作用在一个序列[x1,x2,x3,...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

reduce(f,[x1,x2,x3,x4]) = f(f(f(x1,x2),x3),x4)

比方说对一个序列求和,就可以用reduce实现(但需要注意的是reduce函数在python2中是内置函数,但在python3中非内置函数)

from functools import reduce 
def add(x,y):
    return x+y

print(reduce(add,[1,3,5,7,9]))

把序列[1,3,5,7,9]变换为整数13579,reduce就可以派上用场:

from functools import reduce
def fn(x,y):
    return x * 10 + y
print(reduce(fn,[1,3,5,7,9]))

这个例子本身没多大用处,但是,如果考虑到字符串str也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出把str转换为int的函数:

from functools import reduce
def fn(x,y):
    return x*10 + y
def char2num(s):
    digits = {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}
    return digits[s]
print(reduce(fn,map(char2num,'24678')))

整理成一个str2int的函数就是:

  

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 str2int(s):
    def fn(x,y):
        return x * 10 + y
    def char2num(s):
        return DIGITS[s]
    return reduce(fn,map(char2num,s))
print(str2int('12345'))

还可以用lambda函数进一步简化成:

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 char2int(s):
    return DIGITS[s]
def str2int(s):
    return reduce(lambda x,y: x*10 + y, map(char2num,s))
print(str2int('56789'))

(注:lambda函数是一种匿名函数,它可以用一行代码定义一个简单的函数。它的语法非常简洁,通常用于那些不需要重复使用的函数

基本语法:lambda 参数列表:表达式

例子:add = lambda x,y:x+y  print(add(3,4)) #7

优点:

  • 简洁:代码更紧凑,易于阅读
  • 灵活:可以作为参数传递给其他函数,或者嵌入到更大的表达式中
  • 一次性使用:对于简单的函数,lambda函数可以避免定义一个完整的函数)

回归标题:其实map()和reduce()函数的主要区别就是:map()是对序列依次运算,将一个函数作用于一个序列,以此得到另一个序列;reduce则是累积运算,用于将一个函数依次作用于上次计算的结果和序列的下一个元素,以此得到最终结果。

练习:

利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入['adam','LISA','barT'],输出['Adam','Lisa','Bart']

def normalize(name):
    return name[0].upper() + name[1:].lower()
# 测试:
L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2)

或者用name.title,因为在python中不存在title函数,其是一个字符串对象的方法,作用是:将字符串中的每个单词的首字母大写,其余字母小写。

Python提供的sum()函数可以接收一个list并求和,请编写一个prod()函数,可以接受一个list并利用reduce()求积:

from functools import reduce
def prod(L):
    def fn(x,y):
        return x*y
    return reduce(fn,L)
print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))
if prod([3, 5, 7, 9]) == 945:
    print('测试成功!')
else:
    print('测试失败!')

利用map和reduce编写一个str2float函数,把字符串'123.456转换为浮点数123.456:

from functools import reduce
CHAR_TO_FLOAT = {"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, ".": -1} #首先定义一个"number"对应对应数字的字典,"."对应-1
def str2float(s):
    nums = map(lambda ch: CHAR_TO_FLOAT[ch], s) #用map和lambda把字符s转换为字典
    point = 0 #用于初始化小数点的位置,这里表示还没遇到小数点

    def to_float(f, n):
        nonlocal point #用于声明非全局变量,即在嵌套函数中修改上一层函数的局部变量
        if n == -1: #如果n=-1,表示遇到了小数点,将point设置为1,表示进入小数部分
            point = 1
            return f
        if point == 0: #如果没遇到小数点(point为0),则把之前的f乘以10再加上当前数字n
            return f * 10 + n
        else: #如果point不为0,说明当前数字是小数部分,将point乘以10,将n除以point得到小数部分的值,然后加到f上。
            point = point * 10
            return f + n / point

    return reduce(to_float, nums, 0.0) #初始值为0.0
print('str2float(\'123.456\') =', str2float('123.456'))
if abs(str2float('123.456') - 123.456) < 0.00001:
    print('测试成功!')
else:
    print('测试失败!')

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值