函数式编程¶
来自廖雪峰Python教程后的评论,做一个备份
这里的讨论仅仅涉及将函数作为参数,不涉及函数作为返回值的情况。¶
使用大家之前讨论的一个例子:
求1到100的和
def sum1(start, end):
total = 0
for x in range(start, end + 1):
total += x
return total
求1到100每项平方加1的和
def sum2(start, end):
total = 0
for x in range(start, end + 1):
total += x * x + 1
return total
观察上面的sum1函数和sum2函数,发现非常的相似,都是迭代地加某一个数,然后保存在变量total里,等到函数执行完成后返回该值。唯一不同的地方就是针对被求和的每一项的处理不同:sum1函数中就是每一项x本身,而在sum2函数中,每一项x则是经过x * x + 1或者x ** 2 + 1这样的操作后再参与相加的,而这样的操作可能是多种多样的,比如我要求1到100每个数的立方和,求1到100之间所有素数的和等等,而这个操作应该是独立于我们求和这个概念的:不管我们把每一项变成什么鬼样子,最终的目的就是要把他们全部累积起来。
所以我们可以把针对每一项的具体操作定义为函数,然后将其作为参数传入求和函数sum中
def sum(start, end, handle):
total = 0
for x in range(start, end + 1):
total += handle(x)
return total
其中的handle几句是处理每一项的具体函数,要计算什么素的和,就直接定义处理该数的handle函数即可
现在我们重新来求解上面两个问题:
求1到100的和
# 定义处理的handle函数
# 函数名你可以随便取名
def identity(n):
return n
print(sum(1, 100, identity))
求1到100每项平方加1的和
def square_plus_one(n):
return n ** 2 + 1
print(sum(1, 100, square_plus_one))
如果我们需要计算1到100的立方的和,同理可得
def cube(n):
return n ** 3
print(sum(1, 100, cube))
如果我们需要计算1到100内所有素数的和呢,也是一样的,只是可能我们的handle函数有些复杂而已
# 处理函数is_prime用于判断一个正整数是否为素数
# 如果是素数的话,则返回该数
# 否则则返回0
def is_prime(n):
def smallest_divisor(n):
return find_divisor(n, 2)
def find_divisor(n, test_divisor):
if pow(test_divisor, 2) > n:
return n
elif n % test_divisor == 0:
return test_divisor
else:
return find_divisor(n, test_divisor + 1)
if 1 == n:
retrun 0
elif smallest_divisor(n) == n:
return n
else:
return 0
print(sum(1, 100, is_prime))
其实,如果我们的脑洞在大一点,或者说抽象思维在强一点,就不难发现,其实阶乘和求和其实是类似的概念,区别仅仅在于:
- 将加号更换为乘号
- 接收累积的值的变量total的初始值为0变成了1
那么按照上面的sum求和函数,我们可以很容易的定义求积函数product
def product(start, end, handle):
total = 1
for x in range(start, end + 1):
total *= handle(x)
return total
使用上面定义的product函数,以及上面我们定义的identity函数:
def identity(n):
return n
我们很容易实现阶乘函数factroial函数:
def factroial(n):
return product(1, n, identity)
同样的,如果把上面另外两种求和的情况改为求积的情况,也是非常容易的:仅需要将sum函数更换为product函数即可,其他的都不用改变!
其实,如果我们脑洞再够大一点的话,还能抽象出求和和求积表达的其实是一种更一般的概念:累积,简单的理解就是把一堆东西不断堆在一起,越变越大,不断膨胀的一种情况,求和和求积仅仅是两种特殊的累积方式罢了。
如果有了这样的想法,那我们其实是可以将求和和求积抽象为更高级更一般的概念:累积
老方法,我们还是通过观察比较sum函数和product函数的相同点和不同点,看看能得到啥:
sum函数
def sum(start, end, handle):
total = 0
for x in range(start, end + 1):
total += handle(x)
return total
product函数
def product(start, end, handle):
total = 1
for x in range(start, end + 1):
total *= handle(x)
return total
通过观察比较,其实也不难发现大部分地方都是相同的,不同的地方也就两个地方:
- 变量total的初始值不同
- 累积的方式不同,一个是用加号进行累加,另一个是用乘号进行累乘
然后呢,还是套路,就像上面我们在抽象1到100的和与1到100的平方加1的和来定义sum函数一样,将不同之处定义为函数,然后将这些函数作为参数传入即可。
因此我们来定义accmuluate函数来表达累积这个概念:
def accumulate(start, end, handle, init_value, combine):
def symbol(a, b, combine):
if '+' == combine:
return a + b
elif '*' == combine:
return a * b
else:
pass # 错误处理:
total = init_value
for x in range(start, end + 1):
total = symbol(total, handle(x), combine)
return total
有了这样一个更一般的函数,那我们来重新定义sum函数和product函数就轻松了许多:
def sum(start, end, handle):
return accumulate(start, end, handle, 0, '+')
def product(start, end, handle):
return accumulate(start, end, handle, 1, '*')
最后将完整的代码贴在这里,并计算1到100的和以及1到6的乘积:
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
def accumulate(start, end, handle, init_value, combine):
def symbol(a, b, combine):
if '+' == combine:
return a + b
elif '*' == combine:
return a * b
else:
pass # 错误处理
total = init_value
for x in range(start, end + 1):
total = symbol(total, handle(x), combine)
return total
def sum(start, end, handle):
return accumulate(start, end, handle, 0, '+')
def product(start, end, handle):
return accumulate(start, end, handle, 1, '*')
def identity(n):
return n
print(sum(1, 100, identity))
print(product(1, 6, identity))
运行结果:
root@kali:~/py3venv/scripts# python accumulate.py
5050
720
这里仅仅是我学习函数式编程时的一点点思考,参考的资料是SICP第一章第三节第一小节的内容,有兴趣的同学可以看看,但是Python可能有更简单更强大的方法来处理此类问题,但是编程思想的学习和探究应该是极其重要的。
简化¶
通过使用Python强大的map/reduce函数,上述代码将会大大的简化,这里只是定义Sum函数,Product函数以及他们的更一般抽象的Accumulate函数
# 为了不与Python提供的sum内置函数混淆
# 将我们自己定义的求和函数命名为Sum
def Sum(start, end, handle):
return reduce(lambda x, y: x + y, \
map(lambda x: handle(x), \
[x for x in range(start, end + 1)]))
def Product(start, end, handle):
return reduce(lambda x, y: x * y, \
map(lambda x: handle(x), \
[x for x in range(start, end + 1)]))
从上述Sum函数与Product函数我们可以抽象出Accumulate函数:
def Accumulate(start, end, handle, combine):
def symbol(a, b, combine):
if '+' == combine:
return a + b
elif '*' == combine:
return a * b
else:
pass
return reduce(lambda x, y: symbol(x, y, combine), \
map(lambda x: handle(x), \
[x for x in range(start, end + 1)]))
而通过Accumulate函数,我们又能容易地定义Sum函数和Product函数:
def Sum(start, end, handle):
return accumulate(start, end, handle, '+')
def Product(start, end, handle):
return accumulate(start, end, handle, '*')
最后我们来测试一下计算1到100的和以及1到6的乘积来验证上述函数是否正确
# 定义handle函数
def identity(n):
return n
测试结果:
>>> print(Sum(1, 100, identity))
5050
>>> print(Product(1, 6, identity))
720