Python进阶编程:编写更高效、优雅的Python代码
上QQ阅读APP看书,第一时间看更新

3.1.5 过滤序列元素

在实际应用中,我们经常需要根据指定规则从序列中提取需要的值,或者根据规则缩短序列,即对序列做过滤。

最简单的过滤序列元素的方法是使用列表推导,示例如下:


exp_list = [1, 4, -5, 10, -7, 2, 3, -1]
print([n for n in exp_list if n > 0])
print([n for n in exp_list if n < 0])

使用列表推导的一个潜在缺陷是,如果输入非常大,会产生一个非常大的结果集,占用大量内存。如果对内存比较敏感,那么可以使用生成器表达式迭代产生过滤的元素,示例如下:


pos_items = (n for n in exp_list if n > 0)
for item in pos_items:
    print(item)

有时候,过滤规则比较复杂,如过滤的时候需要处理一些异常或者其他复杂情况,不能简单地在列表推导或者生成器表达式中表达出来。这时可以将过滤代码放到一个函数中,然后使用内置的filter()函数,示例如下:


val_list = ['1', '2', '-3', '-', '4', 'N/A', '5']
def is_int(val):
    try:
        int(val)
        return True
    except ValueError:
        return False
new_val_list = list(filter(is_int, val_list))
print(new_val_list)

filter()函数创建了一个迭代器,因此想得到列表的话,就得像示例那样使用list()函数去转换。

通常情况下,列表推导和生成器表达式是过滤数据最简单的方式。它们还能在过滤的时候转换数据,示例如下:


import math
print([math.sqrt(n) for n in exp_list if n > 0])

过滤操作的一个变种是将不符合条件的值用新的值代替,而不是丢弃它们。如在一列数据中可能不仅想找到正数,还想将不是正数的数替换成指定的数。通过将过滤条件放到条件表达式中去,可以很容易地解决这个问题,示例如下:


print([n if n > 0 else 0 for n in exp_list])
print([n if n < 0 else 0 for n in exp_list])

另外一个值得关注的过滤工具就是itertools.compress(),它以一个iterable对象和一个相对应的Boolean选择器序列作为输入参数,然后输出iterable对象中对应选择器为True的元素。当需要用另一个相关联的序列来过滤某个序列的时候,这个函数是非常有用的,示例如下:


done_work = [
    'read book',
    'running',
    'work',
    'basketball',
    'table tennis',
    'bike',
    'read 20 pages',
    'running 5km',
]
counts = [ 0, 3, 10, 4, 1, 7, 6, 1]

现在想将那些对应count值大于5的地址全部输出,可以这样写代码:


from itertools import compress
more5 = [n > 5 for n in counts]
print(more5)
print(list(compress(done_work, more5)))

这里的关键点在于先创建一个Boolean序列指示哪些元素符合条件,然后通过compress()函数根据Boolean序列去选择输出对应位置为True的元素。

compress()函数返回的是一个迭代器。如果要得到一个列表,需要使用list()函数将结果转换为列表类型。