Python 拆包和映射数据,当使用类似于(f(x) for x, y in C)
这样的结构时,我们使用for
语句中的多重赋值拆开一个元组,并将一个函数应用于其中一部分数据。整体上这是一个映射。这是Python常用的改变结构和应用函数的优化方法。
继续使用第4章中的旅行数据。下面是一个拆包并映射的具体实现。
from typing import Callable, Iterable, Tuple, Iterator, Any
Conv_F = Callable[[float], float]
Leg = Tuple[Any, Any, float]
def convert(
conversion: Conv_F,
trip: Iterable[Leg]) -> Iterator[float]:
return (
conversion(distance) for start, end, distance in trip
)
该高阶函数使用下面的转换函数处理原始数据。
to_miles = lambda nm: nm * 5280 / 6076.12
to_km = lambda nm: nm * 1.852
to_nm = lambda nm: nm
有了转换函数,就可以提取距离数据,再进行转换了,如下所示:
convert(to_miles, trip)
处理结果是一系列浮点数,如下所示:
[20.397120559090908, 35.37291511060606, ..., 44.652462240151515]
从convert()
函数的for
语句不难看出,这个函数只适用于“起点-终点-距离”格式的旅行数据。
可以进一步抽象这类“拆包-映射”设计模式,整个过程稍复杂。首先需要定义通用分解函数,如下所示:
fst = lambda x: x[0]
snd = lambda x: x[1]
sel2 = lambda x: x[2]
要表达f(sel2(s_e_d)) for s_e_d in trip
,就要用到复合函数。我们把一个类似于to_miles()
的函数和一个类似于sel2()
的选择器组合在一起。在Python中,可以用匿名函数把函数组合在一起,如下所示:
to_miles = lambda s_e_d: to_miles(sel2(s_e_d))
这样就得到了一个更长,但更通用的实现,如下所示:
(to_miles(s_e_d) for s_e_d in trip)
这个版本更具通用性,不过用处似乎不太大。
对于上述convert()
高阶函数,需要特别注意的是,它使用函数作为参数,并返回生成器函数作为结果。convert()
函数不是生成器函数,不会生成任何值,它的返回值是一个生成器表达式,需要对其求值才能得到具体的计算结果。我们使用Iterator[float]
来强调返回结果是迭代器,也就是
Python生成器函数的子类。
在这个设计模式中,可以用多重过滤代替映射,实现方法是在返回的生成器表达式中,用if
从句实现过滤功能。
映射和过滤结合可以生成更复杂的函数。创建复杂的函数来缩短处理过程似乎是个好主意,但实际情况并非总是如此。复杂函数的性能往往不如嵌套的map()
函数和filter()
函数。总体而言,只有在函数通过封装概念来降低软件开发复杂度的情况下,才考虑使用复杂函数。