Python编写纯函数使用方法
什么是纯函数?
在计算机科学中,纯函数是指:
- 不产生任何副作用。
- 对于给定的输入,总是返回相同的输出。
所谓副作用,是指函数修改了除了函数输入参数以外的任何状态或者进行了与输入之外的交互,例如打印、修改全局变量等。纯函数相较于有副作用的函数更易于测试、维护和并行处理。
如何编写纯函数?
避免使用全局变量
纯函数应该完全依赖于输入参数,并返回一个新的值作为输出。变量的值不应该依赖于运行流程中任何全局状态的变化。这样的话,当函数在不同上下文中调用时,不会出现因全局变量不同而导致的错误。
避免修改输入参数
纯函数应该不会修改传递给它的输入参数。如果必须在函数体内部修改输入参数,则应该进行一些复制以确保在输入参数外不会出现副作用。
避免I/O操作
纯函数通常不会进行I/O操作(如读写文件、网络请求等),因为这样做会对运行环境产生副作用,同时也会增加函数的复杂度。但我们也可以使用一些函数编程的技巧来将I/O操作隔离在特定的层中,从而避免它们对纯函数的影响。
示例1: 累加器函数
以下是一个非纯函数实现的累加器函数:
total = 0
def add(num):
global total
total += num
return total
上述代码使用全局变量记录当前的总和,每次调用add()
函数时,它都会将传递的值参数加到总和上面。该函数的输入依赖于全局变量,并且每次调用时,它会修改这个全局变量的状态,因此这是一个有副作用的函数。
下面是使用纯函数实现的累加器函数:
def add(num, total=0):
return total + num
在纯函数实现中,用一个额外的参数来保存当前的总和,而不是使用全局变量。每次调用add()
函数时,我们将传递的值加到总和上面,并将结果返回。这个函数不会引入任何副作用,也会对任何输入产生相同的输出。
示例2: 斐波那契数列
斐波那契数列通常使用递归函数来实现计算,但是在递归过程中,函数可能会修改全局状态,例如内存堆栈,在大量计算时可能会导致栈溢出的问题。下面是一个通常的递归实现:
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
这是一个有副作用的函数,因为它在递归过程中修改了内存堆栈。此外,对于一组特定的输入值,该函数需要进行大量的重复计算,这也导致它的效率很低。
一个更好的做法是,将计算出的斐波那契数列值缓存下来。这里使用一个字典来缓存已经计算出来的值,对于已经计算过的值直接从字典中获取,避免了重复计算。
def fib(n, memo={}):
if n in memo:
return memo[n]
elif n == 0:
return 0
elif n == 1:
return 1
else:
result = fib(n-1) + fib(n-2)
memo[n] = result
return result
这个函数和之前的实现相比,效率大大提高,同时也不会对运行环境产生任何副作用。
总结
纯函数是一个好的编程实践,它可以提高代码的稳定性和可维护性,同时也方便进行测试和并行处理。我们学习了一些编写纯函数的技巧和方法,并且给出了两个实例。在实践编程时,我们应该尽量避免引入副作用,并尽可能地将函数设计成不变式函数(函数的输出不会随着时间的变化而改变)。