Python中的函数式编程
函数式编程是Python中的一种编程方式。它把计算看作是数学函数的计算,并且避免使用状态和可变数据。我正在努力理解Python是如何融入函数式编程的。
来看下面这个计算阶乘的程序(factorial.py
):
def factorial(n, total):
if n == 0:
return total
else:
return factorial(n-1, total*n)
num = raw_input("Enter a natural number: ")
print factorial(int(num), 1)
这段代码避免了可变数据,因为我们没有改变任何变量的值。我们只是用一个新的值递归地调用阶乘函数。
- 如果上面关于函数式编程的例子是正确的,那么避免状态是什么意思呢?
- 函数式编程是否意味着在进行计算时我只能使用函数(就像上面的例子那样)?
- 如果给出的例子是错误的,那有什么简单的例子和解释呢?
4 个回答
基础知识
函数式编程是一种编程方式,我们只使用表达式而不使用语句。语句像是条件判断(if),而表达式则是计算数学内容。我们尽量避免可变性(值被改变),因为我们只想要纯函数。纯函数没有副作用,这意味着一个函数在给定相同输入时,总是会产生相同的输出。我们希望函数是纯的,因为这样更容易调试。通过这种方式,我们在描述一个函数是什么,而不是给出如何做某事的步骤,同时我们写的代码也少了很多。函数式编程通常被称为声明式,而其他方法则被称为命令式。
实用工具
了解内置函数,因为它们非常有用。首先介绍一些:abs(绝对值),round(四舍五入),pow(幂),max(最大值),min(最小值),sum(求和),len(长度),sorted(排序),reversed(反转),zip(压缩),和 range(范围)。
匿名函数
匿名函数或称为lambda是纯函数,它们接受输入并产生一个值。这里没有返回语句,因为我们返回的是我们正在计算的结果。你也可以通过声明一个变量来给它们命名。
(lambda x, y: x + y)(1, 1)
add = lambda x, y: x + y
add(1, 1)
三元运算符
因为我们在使用表达式,所以我们用三元运算符来处理逻辑,而不是使用if语句。
(expression) if (condition) else (expression2)
映射、过滤和归约
我们还需要一种循环的方法。这可以通过列表推导来实现。在这个例子中,我们给每个数组元素加一。
inc = lambda x: [i + 1 for i in x]
我们也可以对满足条件的元素进行操作。
evens = lambda x: [i for i in x if (i % 2) == 0]
有些人说这是正确的Python风格。但更严谨的人使用另一种方法:map(映射),filter(过滤),和reduce(归约)。这些是函数式编程的基础。虽然map和filter是内置函数,但reduce以前是内置的,现在在functools模块中。要使用它,先导入:
from functools import reduce
Map是一个函数,它接受一个函数并对数组的每个元素调用它。map函数的结果通常不易读,所以你需要把它转换成元组或集合。
inc = lambda x: tuple(map(lambda y: y + 1, x))
Filter是一个函数,它对数组调用一个函数,保留输出为True的元素,移除输出为False的元素。和map一样,它的结果也不易读。
evens = lambda x: tuple(filter(lambda y: (y % 2) == 0, x))
Reduce接受一个有两个参数的函数。一个是上次调用的结果,另一个是新值。它会一直这样做,直到归约成一个值。
from functools import reduce
m_sum = lambda x: reduce(lambda y, z: y + z, x)
Let和do
很多函数式编程语言都有do。Do是一个函数,它接受n个参数,计算所有参数并返回最后一个的值。Python没有do,但我自己创建了一个。
do = lambda *args: tuple(map(lambda y: y, args))[-1]
这里有一个使用do的例子,它打印一些内容并退出。
print_err = lambda x: do(print(x), exit())
我们使用do来获得命令式的优势。
Let允许某个表达式在另一个表达式中等于某个值。在Python中,大多数人这样做。
def something(x):
y = x + 10
return y * 3
Python 3.8增加了表达式赋值运算符 :=。所以现在代码可以写成lambda的形式。
something = lambda x: do(y := x + 10, y * 3)
与不纯系统的工作
控制台、文件系统、网络等都是不可变且不纯的,我们需要一种方法来处理它们。在一些语言中,比如Haskell,你有monads,它们是函数的包装器。Clojure允许使用不纯函数。在Python这个多范式语言中,我们不需要任何特别的东西,因为它不仅仅是函数式的。打印就是一个不纯的函数。
递归
你的例子使用了递归。递归使用调用栈,这是一个小块内存,用于调用函数。调用栈可能会满并导致崩溃。很多函数式编程语言使用一种称为惰性求值的技术来帮助解决递归问题,但Python没有这种特性。所以尽量避免使用递归。
答案
避免状态就是保持不可变,意味着不改变某个值。
在函数式编程中,一切都是表达式,也就是函数。不过在Python中并不是这样,因为它是多范式的。
用更函数式的方式,你的代码可以这样写。而且由于使用了if语句而不是三元表达式,所以它不是函数式的。
from functools import reduce
factorial = lambda x: reduce(lambda y, z: y*z, range(1,x+1))
这会生成从1到x的范围,并使用range乘以所有值。
如果上面的例子是错的,请提供一个简单的例子并解释一下。
这个例子是对的,它展示了一个实际的问题。如果你用一个很大的数字去调用 factorial
,你会发现会达到最大递归深度。Python没有尾调用优化的功能。
如果上面提到的函数式编程的例子是正确的,那么避免状态是什么意思呢?
这意味着(在Python中)一旦给一个变量赋值后,你应该不再给它重新赋值,或者改变你之前赋给它的值。
其次,函数式编程是否意味着我在进行计算时必须只使用函数(就像上面的例子所示)?
函数式编程的概念其实很广泛。Python是一种多范式的语言,支持一些函数式编程的概念。
函数式编程意味着所有的计算都应该被看作是数学函数。
我写了一篇文章,详细解释了以上内容:在Python中使用函数式编程
这段内容引用了一个演讲,里面比较了命令式编程和函数式编程的思维方式。这里的例子是借用的。
命令式编程:
expr, res = "28+32+++32++39", 0
for t in expr.split("+"):
if t != "":
res += int(t)
print res
函数式编程:
from operator import add
expr = "28+32+++32++39"
print reduce(add, map(int, filter(bool, expr.split("+"))))
这个例子在函数式编程中是正确的。但是它在Python中是一个不应该这样做的好例子,因为这样做效率低下,而且不容易扩展。Python没有尾调用优化,所以不应该仅仅为了避免使用传统的循环而使用递归调用。如果你真的开始在Python中用这种风格编程,你的程序最终会出现运行时错误。
你描述的是纯函数式编程,而这并不是Python能够很好支持的。
Python在某种程度上支持函数式编程,因为函数被视为一等公民。这意味着函数可以作为参数传递给其他函数,也可以作为结果从函数中返回。而且Python的标准库中包含了一些在大多数函数式编程语言的标准库中也能找到的函数,比如map()
、filter()
、reduce()
,还有在functools和itertools模块中的一些工具。