Python 集合的尾调用优化,处理集合大致有两种方法:使用高阶函数,返回一个生成器表达式,或者创建一个函数,用for
循环依次处理集合中的每个值。这两种方法实际上很类似。
下面是一个类似于Python内置map()
函数的高阶函数:
from typing import Callable, Iterable, Iterator, Any, TypeVar
D_ = TypeVar("D_")
R_ = TypeVar("R_")
def mapf(
f: Callable[[D_], R_],
C: Iterable[D_]
) -> Iterator[R_]:
return (f(x) for x in C)
返回结果是一个实现了映射处理的生成器表达式,用显式for
循环实现了类似于尾调用优化的效果。
数据源C
通过类型标示Iterable[D_]
强调映射域的类型。映射函数的类型标示是Callable[[D_], R_]
,表示将某个领域类型映射为某个范围类型。以float()
函数为例,它将字符串域中的值映射到浮点数范围内。返回结果的类型标示是Iterator[R_]
,是基于某个范围类型(即映射函数的结果类型)的迭代器。
实现了相同功能,具备相同类型签名的生成器函数如下:
def mapg(
f: Callable[[D_], R_],
C: Iterable[D_]
) -> Iterator[R_]:
for x in C:
yield f(x)
这里使用了完整的for
语句来实现尾调用优化。返回结果与前面的实现一致。由于包含多条语句,运行速度略慢。
不论使用哪种方式,返回结果都是可迭代对象,必须通过某种方法从可迭代的数据源中实例化出一个序列对象,例如下面利用list()
函数实现实例化:
>>> list(mapg(lambda x: 2 ** x, [0, 1, 2, 3, 4]))
[1, 2, 4, 8, 16]
为了保证性能和伸缩性,需要对Python程序进行这类尾调用优化处理。虽然这种实现不是纯粹的函数式,但它带来的好处远抵过缺少纯粹性的损失。为了获得函数式设计简洁明了的优势,需要将这类不够纯粹的函数当作普通的递归使用。
这意味着在编程实践中应避免在集合处理函数中混入有状态的处理步骤(这会弄乱整个处理流程)。这样即使处理流程的某些部分不是纯粹的函数式,从整个处理流程看,函数式编程的核心原则仍然适用。