Python 不可变对象,函数式编程中不能使用变量跟踪计算的状态,所以我们需要研究如何使用不可变对象,比如可以使用元组和命名元组构建复杂的不可变数据结构。
不可变对象的概念在Python中并不陌生。程序使用不可变元组比使用可变对象的性能要好。在某些情况下,使用不可变对象时,我们需要重新考虑算法,以避免改变对象所带来的开销。
我们将(几乎)完全避免使用类定义,虽然在一门面向对象编程的语言里这么做似乎不合逻辑。通过阅读本书你会了解,函数式编程并不需要有状态的对象。可以定义可调用对象,并通过它们把互相关联的函数放在同一个命名空间内,这类对象可以在多个级别上进行配置。通过可调用对象创建缓存也很简单,而且能大幅提升性能。
下面介绍一个处理不可变对象的常用设计模式:wrapper()
函数。由元组组成的列表是常见的数据结构,我们经常用以下两种方式处理它们。
- 使用高阶函数:如前所述,为
max()
提供一个lambda
表达式——max(year_cheese, key=lambda yc: yc[1])
。 -
使用“打包-处理-拆包”模式:在函数式语境中,这种模式可以表述为
unwrap(process (wrap(structure)))
。
例如,看看以下命令片断。
>>> max(map(lambda yc: (yc[1], yc), year_cheese))[1]
(2007, 33.5)
这个例子很好地展示了上面说的三部曲模式:打包数据结构,获取打包后数据结构的最大值,然后拆包。
首先是打包。map(lambda yc: (yc[1],yc), year_cheese)
把列表中的每一项转换成一个二元组,其中用于比较的项后面跟着原始项,这里用于比较的项是yc[1]
。
接下来用max()
函数处理逻辑。因为之前已经把需要比较的项变成了二元组的第一个元素,所以使用max()
的默认方式就可以了,不再需要它的高阶函数能力。
最后用下标[1]
提取最终结果,即拆包。这里是从max()
返回的结果中通过取第二个元素得到目标元组。
这类打包、拆包的操作在函数式编程中很常用,所以有些函数式语言为这类操作提供了专门的函数,例如fst()
和snd()
等,这样就可以使用前缀式语法,而不必使用[0]
或[1]
这样的下标了。我们可以实现这些函数,并将其应用于“打包-处理-拆包”模式中,如下所示。
>>> snd = lambda x: x[1]
>>> snd(max(map(lambda yc: (yc[1], yc), year_cheese)))
(2007, 33.5)
上例中,通过定义snd()
函数实现取元组第二个元素的功能,这样实现的“打包-处理-拆包”模式更易读。我们使用map(lambda... , year_cheese)
打包原始数据项,使用max()
进行处理,最后用snd()
函数从返回的元组中提取第二个元素。