16.3. 过滤已访问列表

你已经熟识了 应用列表遍历来过滤列表。 这里介绍的是达到相同效果的另一种令很多人感觉清晰的实现方法。

Python 有一个内建 filter 函数,它接受两个参数:一个函数和一个列表,返回一个列表。[7] 作为第一个参数传递给 filter 的函数本身应接受一个参数,filter 返回的列表将会包含被传入列表参数传递给 filter 所有可以另函数返回真(true)的元素。

都明白了吗?并没有听起来那么难。

例 16.7. 介绍 filter

>>> def odd(n):                 1
...     return n % 2
...     
>>> li = [1, 2, 3, 5, 9, 10, 256, -3]
>>> filter(odd, li)             2
[1, 3, 5, 9, -3]
>>> [e for e in li if odd(e)]   3
>>> filteredList = []
>>> for n in li:                4
...     if odd(n):
...         filteredList.append(n)
...     
>>> filteredList
[1, 3, 5, 9, -3]
1 odd 使用内建的取模(mod)函数 “%” 对于为奇数的 n 返回 True ;为偶数的返回 False
2 filter 接受两个参数:一个函数(odd)和一个列表(li)。 它依列表循环为每个元素调用 odd 函数。 如果 odd 返回的是真(记住, Python 认为所有非零值为真),则该元素被放在返回列表中,如若不然则被过滤掉。 结果是一个只包含原列表中奇数的列表,出现顺序则和原列表相同。
3 你可以通过遍历的方式完成相同的工作,正如在 第 4.5 节 “过滤列表” 中看到的。
4 你可以通过 for 循环的方式完成相同的工作。 根据你编程的背景,这样也许更“直接”,但是像 filter 函数这样的实现方法更清晰。 不但编写简单,而且易于读懂。 for 循环就好比近距离的绘画:你可以看到所有的细节,但是或许你应该花几秒时间退后几步看一看图画的全景。 “啊,你仅仅是要过滤列表!

例 16.8. regression.py 中的 filter

    files = os.listdir(path)                                1
    test = re.compile("test\.py$", re.IGNORECASE)           2
    files = filter(test.search, files)                      3
1 正如你在 第 16.2 节 “找到路径” 中看到的, path 可能包括正在运行脚本的完全或者部分路径名,或者当脚本运行自当前目录时包含一个空的字符串。 任何一种情况下, files 都会获得正运行脚本所在目录的文件名。
2 这时一个复杂的正则表达式。正如你在 第 15.3 节 “重构”中看到的,如果你需要反复使用同一个正则表达式,你应该编译它已获得更快的性能。编译后的对象将含有接受一个被寻找字符串作为参数的 search 方法。 如果这个正则表达式匹配字符串, search 方法返回一个包含正则表达式匹配信息的 Match 对象;否则返回 None, 这是 Python 空(null) 值。
3 对于 files 列表中的每个元素,你将会调用正则表达式编译对象 testsearch 方法。 如果正则表达匹配,方法将会返回一个被 Python 认定为真(true)的 Match 对象;如果正则表达不匹配, search 方法将会返回被认定为假(false)的 None,元素将被排除。

历史注释. Python 2.0 早期的版本不包含 列表遍历,因此不能 以列表遍历方式过滤filter 函数是当时唯一的方法。 即便是在引入列表遍历的 2.0 版,有些人仍然钟情于老派的 filter (和这章稍后将见到的它的伴侣函数 map ) 两种方法并存于世,使用哪种方法只是风格问题, mapfilter 将在未来的 Python 版本中被废止的讨论尚无定论。

例 16.9. 以列表遍历法过滤

    files = os.listdir(path)                               
    test = re.compile("test\.py$", re.IGNORECASE)          
    files = [f for f in files if test.search(f)] 1
1 这种方法将完成和 filter 函数完全相同的工作。 哪种方法更清晰完全取决于你自己。

Footnotes

[7] 从技术层面上讲, filter 的第二个参数可以是任意的序列,包括列表、元组以及定义了 __getitem__ 特殊方法而能像列表一样工作的自定义类。 在可能情况下, filter 会返回与输入相同的数据类型,也就是过滤一个列表返回一个列表,过滤一个元组返回一个元组。