16.3. 再次探讨列表过滤

您已经熟悉使用列表推导式过滤列表。还有一种方法可以实现同样的功能,有些人认为这种方法更具表达力。

Python 有一个内置的 filter 函数,它接受两个参数,一个函数和一个列表,并返回一个列表。[7] 作为第一个参数传递给 filter 的函数本身必须接受一个参数,并且 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 使用内置的模运算符 “%” 来返回 True(如果 n 为奇数)和 False(如果 n 为偶数)。
2 filter 接受两个参数,一个函数 (odd) 和一个列表 (li)。它循环遍历列表,并使用每个元素调用 odd。如果 odd 返回一个真值(请记住,在 Python 中,任何非零值都是真),则该元素将包含在返回的列表中,否则将被过滤掉。结果是一个列表,其中只包含原始列表中的奇数,顺序与它们在原始列表中出现的顺序相同。
3 您可以使用列表推导式完成相同的操作,如您在第 4.5 节“过滤列表”中所见。
4 您也可以使用 for 循环完成相同的操作。根据您的编程背景,这可能看起来更“直接”,但像 filter 这样的函数更具表达力。不仅更容易编写,也更容易阅读。阅读 for 循环就像站得太靠近一幅画;您看到了所有的细节,但可能需要几秒钟才能后退一步,看到更大的图景:“哦,您只是在过滤列表!

示例 16.8. filterregression.py 中的应用

    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 中的空值。
3 对于 files 列表中的每个元素,您将调用已编译的正则表达式对象 testsearch 方法。如果正则表达式匹配,则该方法将返回一个 Match 对象,Python 认为它是真,因此该元素将包含在 filter 返回的列表中。如果正则表达式不匹配,则 search 方法将返回 NonePython 认为它是假,因此该元素将不会包含在内。

历史注释。 2.0 之前的 Python 版本没有列表推导式,因此您无法使用列表推导式进行过滤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 函数完全相同的结果。哪种方式更具表达力?这取决于您。

脚注

[7] 从技术上讲,filter 的第二个参数可以是任何序列,包括列表、元组和通过定义 __getitem__ 特殊方法来模拟列表行为的自定义类。如果可能,filter 将返回与您提供的相同的数据类型,因此过滤列表将返回列表,但过滤元组将返回元组。