Python 扩展简单循环,对简单循环的扩展包括两种情况。首先介绍过滤式扩展,这种扩展能够按照某种标准舍弃数据源中的某些数据,这些数据可能是异常值,或者存在格式错误。第二种情况是简单转换原始数据,在原有对象的基础上创建新的对象。这里是要将字符串转换为浮点数。不过如何通过映射扩展简单循环要看具体情况。下面介绍重构pairs()
函数的方法,如果想调整点的顺序并舍弃某些值,应该怎么做呢?可以使用过滤式扩展筛选数据。
在之前循环体的实现中,为了将复杂度降到最低,仅仅返回了数据对,没有其他任何应用逻辑相关的处理。实现的简洁避免了开发者在复杂的状态变换中迷失方向。
为循环添加过滤式扩展的一种实现方案如下:
from typing import Iterator, Any, Iterable
Pairs_Iter = Iterator[Tuple[float, float]]
LL_Iter = Iterable[
Tuple[Tuple[float, float], Tuple[float, float]]]
def legs_filter(lat_lon_iter: Pairs_Iter) -> LL_Iter:
begin = next(lat_lon_iter)
for end in lat_lon_iter:
if #some rule for rejecting:
continue
yield begin, end
begin = end
这样在循环体中添加了筛选规则来滤掉某些值,在保持循环简洁明了的前提下,处理过程能够按照预期进行。另外,由于该函数可以处理任何可迭代对象,而不必考虑数据对的具体用途,为它编写测试用例也很简单。
这里没有实现#some rule for rejecting
部分的代码,这部分用于通过begin
、end
或者二者结合过滤掉某些无效数据。例如需要过滤掉begin == end
,以免出现长度为0的线段。
接下来的重构工作是为循环添加映射。在应用功能、设计目标不断变化的过程中,经常需要加入映射。这里的原始数据是字符串类型的,需要将它们转换为浮点数以备后用。这个转换本身不复杂,展示了这种扩展的实现方法。
下面的示例把生成器函数包裹在生成器表达式中,实现了数据映射。
trip = list(
legs(
(float(lat), float(lon))
for lat,lon in lat_lon_kml(row_iter_kml(source))
)
)
legs()
函数的输入参数是一个生成器表达式,该生成器表达式把使用lat_lon_kml()
的输出转换为浮点数。或者反过来说:首先把lat_lon_kml()
函数的输出转换为浮点数对,再转换为一系列路径段。
现在事情变得有点复杂了,几个函数层层嵌套,在数据生成器上分别应用了float()
、legs()
以及tuple()
。对于这类复杂的表达式,常用的重构方法是把生成器表达式从实例化的集合中独立出来,对上述表达式的一种简化实现如下:
flt = (
(float(lat), float(lon))
for lat,lon in lat_lon_kml(row_iter_kml(source))
)
print(tuple(legs(ll_iter)))
这里将生成器函数赋给了变量flt
,该变量不是集合对象,也不是通过列表推导生成的对象,只是给生成器表达式起了个名字(flt
),然后在其他表达式中使用这个名字。
对tuple()
的求值导致作为参数的惰性变量开始求值,flt
变量对应的对象只在需要求值的时候才被创建。
除了上面的代码实现,还有其他方法可以实现重构。数据源经常发生变化。在上面的例子中,lat_lon_kml()
函数与其他表达式紧密绑定,导致处理其他数据源时,难以复用这个函数。
当需要把上面的转换过程float()
参数化,以便之后复用,可以基于生成器表达式专门定义一个函数。下面抽取一些处理过程放在单独的函数里,来对操作过程进行分组。这里由于字符串到浮点数的转换与数据源无关,可以把这个复杂的转换表达式写入下面的函数中。
from typing import Iterator, Tuple, Text, Iterable
Text_Iter = Iterable[Tuple[Text, Text]]
LL_Iter = Iterable[Tuple[float, float]]
def float_from_pair(lat_lon_iter: Text_Iter) -> LL_Iter:
return (
(float(lat), float(lon))
for lat,lon in lat_lon_iter
)
该函数对可迭代对象每组值的第1个数据和第2个数据应用float()
函数,把原始数据转换成由浮点数组成的二元组,这里借助了Python的for
语句解析此二元组。
类型标示要求输入是Text_Iter
类型:Text
类型二元组组成的可迭代对象;返回值是LL_Iter
类型:浮点数二元组组成的可迭代对象。LL_Iter
类型可以用于其他复杂函数的定义中。
该函数可用于如下场景:
legs(
float_from_pair(
lat_lon_kml(
row_iter_kml(source))))
这样就把KML文件中的数据转换为浮点数了。由于每一个处理环节都是一个简单的前缀式函数,可以方便地可视化整个过程,每个函数的输入都是内层嵌套函数的输出。
解析过程的输入值通常是字符串,对于数据处理应用来说,经常需要将输入值转换为浮点数、整数、十进制数等类型,这时就要在清洗源数据的表达式中插入类似于float_from_pair()
的函数。
原先字符串类型的输出如下所示:
(('37.54901619777347', '-76.33029518659048'),
('37.840832', '-76.27383399999999'),
...
('38.976334', '-76.47350299999999'))
所需的浮点型数据如下所示:
(((37.54901619777347, -76.33029518659048),
(37.840832, -76.273834)),
...
((38.330166, -76.458504), (38.976334, -76.473503)))
接下来简化转换流程,前面把代码优化成了flt = ((float(lat), float(lon)) for lat,
lon in lat_lon_kml())
,根据函数替换规则,可以把类似于((float(lat), float (lon)) for lat,lon in lat_lon_kml())
的复杂表达式,替换为返回值相同的函数,这里是float_from_pair(lat_lon_kml())
。这样的重构可以简化复杂的表达式,同时保证功能不变。