
filter函数和列表推导式有什么区别:用示例看懂两种写法
在 Python 中,筛选序列时常见两种写法:内置函数 filter() 和列表推导式。它们都可以根据条件保留部分元素,但返回结果的类型、计算时机和表达能力并不相同。
例如,要从一组数字中筛出偶数,可以这样写:
numbers = [1, 2, 3, 4, 5, 6]
# 使用 filter
evens_a = list(filter(lambda x: x % 2 == 0, numbers))
# 使用列表推导式
evens_b = [x for x in numbers if x % 2 == 0]
print(evens_a) # [2, 4, 6]
print(evens_b) # [2, 4, 6]
两段代码的结果相同,但不代表它们在所有场景下都可以互换。本文会从语法、求值方式、边界行为和实际用途逐步比较,并给出可以直接运行的例子。
一、先看两种写法分别是什么
filter():用函数判断是否保留元素
filter() 是 Python 内置函数,调用形式是:
filter(function, iterable)
function是一个接收单个元素并返回判断结果的函数,也可以传None。iterable是可迭代对象,例如列表、元组、字符串、生成器或字典。- 返回值是一个
filter迭代器,不是列表。
当 function 不是 None 时,filter() 会依次取出可迭代对象中的元素,将元素交给函数判断,只保留函数结果为真值的元素。上面的偶数示例中,判断函数是 lambda x: x % 2 == 0。
也可以把判断函数单独定义出来。对于需要复用或需要描述性名称的条件,这种写法通常比 lambda 更清楚:
def is_even(number):
return number % 2 == 0
numbers = [1, 2, 3, 4, 5, 6]
result = filter(is_even, numbers)
print(list(result)) # [2, 4, 6]
列表推导式:把遍历和条件写在一起
列表推导式常用形式如下:
[表达式 for 变量 in 可迭代对象 if 条件]
其中 if 条件 是可选的。筛选后保留原元素时,表达式一般就是循环变量:
numbers = [1, 2, 3, 4, 5, 6]
evens = [number for number in numbers if number % 2 == 0]
print(evens) # [2, 4, 6]
列表推导式也可以在筛选后转换元素。例如,筛出偶数并将它们平方:
numbers = [1, 2, 3, 4, 5, 6]
squares = [number ** 2 for number in numbers if number % 2 == 0]
print(squares) # [4, 16, 36]
这与单独使用 filter() 的目标略有不同:filter() 负责保留或丢弃元素,本身不负责转换;列表推导式可以在同一表达式中筛选并生成新值。
二、最重要的区别:返回类型与计算时机
列表推导式会立即生成列表
列表推导式执行时,会遍历输入数据,并把符合条件的结果放进一个新列表。因此,计算完成后,结果已经在内存中:
numbers = [1, 2, 3, 4, 5, 6]
evens = [number for number in numbers if number % 2 == 0]
print(type(evens)) # <class 'list'>
这意味着可以直接通过索引访问、查看长度,也可以多次遍历:
evens = [2, 4, 6]
print(evens[0]) # 2
print(len(evens)) # 3
for value in evens:
print(value)
for value in evens:
print(value)
filter() 返回惰性迭代器
filter() 返回一个迭代器。创建它时通常不会立刻把所有元素都筛选并保存起来;元素是在迭代过程中逐个获取和判断的:
numbers = [1, 2, 3, 4, 5, 6]
filtered = filter(lambda x: x % 2 == 0, numbers)
print(type(filtered)) # <class 'filter'>
print(filtered) # 形如 <filter object at ...>
evens = list(filtered)
print(evens) # [2, 4, 6]
惰性求值适合逐步处理数据,尤其是输入较大、只需要取一部分结果,或下游本来就能接收迭代器的情况。但“惰性”不等于数据一定不会占用内存:如果把结果转换成列表,仍然会生成列表;如果把所有结果都消费掉,判断函数也会对相应元素执行。
迭代器一般只能从当前位置向前消费。filter 对象遍历完后,不会自动从头开始:
numbers = [1, 2, 3, 4]
filtered = filter(lambda x: x > 2, numbers)
print(list(filtered)) # [3, 4]
print(list(filtered)) # []
如果需要重复使用结果,可以保存为列表,或者重新创建一个 filter() 对象。不要把已经消费过的迭代器误认为仍包含原来的所有结果。
三、条件判断与表达式:两者能做的事并不完全一样
只筛选、不改变元素时,两者都合适
如果目的就是保留符合条件的原始元素,下面两种写法都可以:
words = ["apple", "", "pear", " ", "orange"]
# filter
non_empty_a = list(filter(lambda word: word != "", words))
# 列表推导式
non_empty_b = [word for word in words if word != ""]
print(non_empty_a) # ['apple', 'pear', ' ', 'orange']
print(non_empty_b) # ['apple', 'pear', ' ', 'orange']
这里条件明确地排除了空字符串,但保留了只包含空格的字符串。如果希望排除空字符串和纯空白字符串,需要写出相应的规则,例如使用 word.strip() 判断:
words = ["apple", "", "pear", " ", "orange"]
non_blank = [word for word in words if word.strip()]
print(non_blank) # ['apple', 'pear', 'orange']
判断条件要符合实际需求。把“为空”和“只包含空格”当成同一件事,是处理字符串时很常见的疏漏。
筛选后还要转换时,列表推导式通常更直观
下面的代码先筛选大于零的数,再将保留的数转换为字符串:
numbers = [-2, 0, 3, 5]
labels = [f"值为 {number}" for number in numbers if number > 0]
print(labels) # ['值为 3', '值为 5']
若先用 filter(),再做转换,通常需要再接一个 map():
numbers = [-2, 0, 3, 5]
positive = filter(lambda number: number > 0, numbers)
labels = map(lambda number: f"值为 {number}", positive)
print(list(labels)) # ['值为 3', '值为 5']
这不是说 filter() 和 map() 不能组合,而是要考虑可读性。转换规则简单且步骤清晰时,推导式往往更容易一眼看懂;若项目中已有命名良好的函数,函数式组合也可能很自然。
多个条件都能写,但不要把条件堆得难以理解
列表推导式可以按从左到右的顺序写多个筛选条件:
numbers = range(1, 21)
selected = [
number
for number in numbers
if number % 2 == 0
if number % 3 == 0
]
print(selected) # [6, 12, 18]
这里两个 if 的含义是:同时满足“能被 2 整除”和“能被 3 整除”。也可以把条件合并成一个布尔表达式:
selected = [
number
for number in range(1, 21)
if number % 2 == 0 and number % 3 == 0
]
filter() 也可以通过一个包含多个逻辑条件的函数完成同样的工作:
def is_divisible_by_six(number):
return number % 2 == 0 and number % 3 == 0
selected = list(filter(is_divisible_by_six, range(1, 21)))
print(selected) # [6, 12, 18]
如果条件分支很多,或判断过程包含多步业务逻辑,建议使用有名称的函数,而不是写成长长的推导式或复杂的 lambda。
四、filter(None, iterable) 到底是什么意思
filter() 的第一个参数可以是 None。此时,它会依据每个元素自身的真值进行筛选,保留真值为真的元素:
values = [0, 1, False, True, "", "hello", None, [], [1, 2]]
result = list(filter(None, values))
print(result) # [1, True, 'hello', [1, 2]]
这类写法看起来简短,但它不是“只删除 None”,也不是“只删除空字符串”。Python 中若干不同类型的值都是假值,包括 False、数值零、空字符串、空列表、空元组、空字典和 None 等。因此,若只想排除 None,应明确写出条件:
values = [0, 1, None, 2]
not_none = [value for value in values if value is not None]
print(not_none) # [0, 1, 2]
如果需求是排除空字符串,但要保留数值零和 False,也不要使用 filter(None, values),而应使用针对目标类型或规则的判断。
对照来看,下面这个推导式可以表达与 filter(None, values) 类似的真值筛选:
values = [0, 1, False, True, "", "hello", None, [], [1, 2]]
result = [value for value in values if value]
print(result) # [1, True, 'hello', [1, 2]]
明确写出筛选意图通常更有利于维护。尤其是在数据中零值或空容器具有业务意义时,不要为了省几个字符而隐式地丢弃它们。
五、适用场景:怎样选更合适
优先考虑列表推导式的情况
- 你需要一个可以直接访问和复用的列表。
- 需要在筛选的同时转换元素,例如提取字段、格式化文本或计算新值。
- 条件简单,直接写在表达式里能让意图更清楚。
- 团队代码约定倾向于使用 Python 常见的推导式风格。
users = [
{"name": "安", "active": True},
{"name": "博", "active": False},
{"name": "澄", "active": True},
]
active_names = [
user["name"]
for user in users
if user["active"]
]
print(active_names) # ['安', '澄']
考虑使用 filter() 的情况
- 已有一个命名清楚、可复用的单参数判断函数。
- 处理结果可以继续作为迭代器传给其他函数或循环消费。
- 不需要立即创建列表,希望按需逐个处理元素。
- 你正在编写自然适合函数式组合的处理流程。
def is_active(user):
return user["active"]
users = [
{"name": "安", "active": True},
{"name": "博", "active": False},
{"name": "澄", "active": True},
]
active_users = filter(is_active, users)
for user in active_users:
print(user["name"])
如果只需要查找第一个符合条件的结果,不一定要先创建完整列表。可以使用 next() 搭配 filter(),并提供默认值以处理没有匹配项的情况:
numbers = [1, 3, 5, 8, 9]
first_even = next(
filter(lambda number: number % 2 == 0, numbers),
None
)
print(first_even) # 8
使用列表推导式也能达到相同目的,不过会继续检查整个输入并构造所有符合条件的结果:
numbers = [1, 3, 5, 8, 9]
evens = [number for number in numbers if number % 2 == 0]
first_even = evens[0] if evens else None
当数据量很大或判断函数有明显开销时,只为找到第一个结果而计算全部结果通常没有必要。若要写成推导式风格,也可以使用生成器表达式,并用 next() 获取首个结果:
numbers = [1, 3, 5, 8, 9]
first_even = next(
(number for number in numbers if number % 2 == 0),
None
)
print(first_even) # 8
六、边界行为:输入对象、顺序和副作用
输入必须是可迭代对象
filter() 和列表推导式都要求输入能够迭代。整数本身不是可迭代对象,下面的代码会报 TypeError:
# 错误:整数不能直接迭代
# result = filter(lambda x: x > 0, 10)
# result = [x for x in 10 if x > 0]
如果想处理从 0 到 9 的整数,可以使用 range():
result = [number for number in range(10) if number > 5]
print(result) # [6, 7, 8, 9]
元素顺序会按输入顺序保留
对于普通迭代过程,这两种写法都按照输入迭代器产出元素的顺序处理,并按该顺序给出保留结果。它们不会自动排序,也不会自动去重:
numbers = [4, 2, 4, 1, 2]
print(list(filter(lambda x: x > 1, numbers)))
# [4, 2, 4, 2]
print([x for x in numbers if x > 1])
# [4, 2, 4, 2]
若需要去重或排序,应单独表达这些要求。例如,使用集合去重可能会改变顺序,不能把它当成只筛选的替代品。
判断函数应尽量避免副作用
筛选函数通常应该专注于判断并返回布尔结果。如果它同时修改外部变量、写文件或发起网络请求,惰性迭代带来的执行时机可能让行为不够直观。
例如,下面的计数是在消费 filter 结果时逐步发生的,而不是在创建 filter 对象时发生:
calls = 0
def is_even(number):
global calls
calls += 1
return number % 2 == 0
numbers = [1, 2, 3, 4]
filtered = filter(is_even, numbers)
print(calls) # 0
print(next(filtered)) # 2
print(calls) # 2:为找到第一个保留值,检查了 1 和 2
print(list(filtered)) # [4]
print(calls) # 4
列表推导式则会在创建列表的过程中执行筛选函数。依赖这种执行时机容易引入难以排查的问题,通常应避免把有重要副作用的操作放进过滤条件中。
七、常见错误与误解
误以为 filter() 会返回列表
在 Python 3 中,filter() 返回迭代器。需要列表时应显式转换:
numbers = [1, 2, 3, 4]
result = list(filter(lambda x: x > 2, numbers))
print(result) # [3, 4]
某些旧版 Python 教材或代码会写出与当前环境不同的返回类型结论。编写现代 Python 代码时,应按 Python 3 的迭代器行为理解和使用 filter()。
忘记消费 filter 迭代器
直接打印迭代器只会显示对象信息,不会显示筛选后的元素。可以使用循环遍历,也可以根据需要转换成列表:
numbers = [1, 2, 3, 4]
result = filter(lambda x: x > 2, numbers)
for number in result:
print(number)
把 filter 的条件函数写成不返回结果的函数
如果函数没有显式 return,Python 会让它返回 None。由于 None 是假值,使用这样的函数筛选时,元素都会被过滤掉:
def is_positive(number):
number > 0 # 错误:没有 return
values = [1, 2, 3]
print(list(filter(is_positive, values))) # []
正确写法应返回判断结果:
def is_positive(number):
return number > 0
values = [1, 2, 3]
print(list(filter(is_positive, values))) # [1, 2, 3]
把推导式里的 if 与条件表达式混淆
列表推导式末尾的 if 用来决定是否保留当前元素:
values = [number for number in range(6) if number % 2 == 0]
print(values) # [0, 2, 4]
而条件表达式会为每个元素选择一个输出值,通常写作 值A if 条件 else 值B:
labels = [
"偶数" if number % 2 == 0 else "奇数"
for number in range(4)
]
print(labels) # ['偶数', '奇数', '偶数', '奇数']
两者可以同时出现,但含义不同。下面的代码先筛选偶数,再把保留的元素转换为字符串:
labels = [
"偶数" if number % 2 == 0 else "奇数"
for number in range(6)
if number > 1
]
print(labels) # ['偶数', '奇数', '偶数', '奇数']
为了简短使用过于复杂的 lambda
复杂的判断逻辑写进 lambda 往往不容易阅读,也难以加入注释或测试。下面的写法就不太适合长期维护:
# 不推荐:条件复杂时不易理解
selected = filter(
lambda item: item is not None and item.get("enabled") and item.get("score", 0) >= 60,
records
)
可以改为具名函数:
def is_passing_enabled_record(item):
if item is None:
return False
if not item.get("enabled"):
return False
return item.get("score", 0) >= 60
selected = filter(is_passing_enabled_record, records)
或者在规则简单且表达式仍然易读时使用推导式。选择的重点不是“哪种形式更高级”,而是让筛选意图清楚、便于后续修改。
八、性能与内存:不要只凭写法下结论
列表推导式会直接生成列表;filter() 则提供惰性迭代器。这是两者在资源使用上的主要区别。若结果很大而且可以逐个消费,迭代器能避免一次性保存全部筛选结果。若需要反复访问结果,列表更方便,但会占用保存结果所需的内存。
两者谁更快,不能脱离实际代码一概而论。执行速度会受到 Python 版本、判断函数、数据大小、后续操作等因素影响。不要仅仅因为 filter() 是内置函数,或因为推导式看起来更简短,就断言某一种在所有场景下都更快。性能敏感时,应该用符合真实工作负载的数据进行测量,同时确认比较的工作量和结果处理方式一致。
还要注意,列表推导式会把结果全部保存在内存中;但如果 filter() 的结果最终被 list() 完整转换,结果同样会整体进入内存。惰性求值能否带来实际好处,取决于后续是否按需消费,以及数据源本身的行为。
九、filter() 的参数限制与版本说明
filter(function, iterable) 接收两个参数:第一个是函数或 None,第二个是可迭代对象。传入的判断函数通常会以一个位置参数调用,也就是每次接收一个待检查元素;若函数签名不接受这个参数,或它还要求未提供的必需参数,迭代时就会报错。
def is_greater_than(value, limit):
return value > limit
numbers = [1, 3, 5]
# 错误:filter 只会把每个元素作为一个参数传给函数
# result = filter(is_greater_than, numbers)
可以用闭包、偏函数,或定义参数已经固定的函数来处理额外配置。下面通过嵌套函数固定阈值:
def make_greater_than(limit):
def check(value):
return value > limit
return check
numbers = [1, 3, 5]
result = list(filter(make_greater_than(2), numbers))
print(result) # [3, 5]
也可以使用标准库中的 functools.partial 固定一个参数:
from functools import partial
def is_greater_than(value, limit):
return value > limit
numbers = [1, 3, 5]
check = partial(is_greater_than, limit=2)
print(list(filter(check, numbers))) # [3, 5]
不要把“函数需要多个参数”误解成 filter() 能自动从元素中拆出多个参数。假如迭代对象中的元素是元组,通常要让判断函数接收这个元组,再在函数内部读取字段:
records = [
("安", 72),
("博", 55),
("澄", 91),
]
def passed(record):
name, score = record
return score >= 60
print(list(filter(passed, records)))
# [('安', 72), ('澄', 91)]
filter() 不会自动并行执行,也不会自动异步等待判断函数。传入普通同步函数时,它按迭代过程调用该函数;若任务涉及异步迭代或并发,需要使用与具体异步流程相匹配的工具,不能仅靠把 async 函数传给 filter() 就获得异步筛选行为。
十、筛选数据不等于安全校验
用 filter() 或列表推导式筛选数据,只是程序中的一种数据处理操作。它本身不会自动验证输入来源、保证数据可信,也不能代替权限检查、类型校验、业务规则验证或安全处理。
尤其不要把字符串筛选当成防注入措施。例如,简单地移除包含某些字符的输入,通常无法可靠阻止 SQL 注入、跨站脚本或命令注入。安全处理应根据数据进入的具体位置选择对应方式:数据库查询使用参数化查询,输出到 HTML 时进行适合上下文的转义,调用外部命令时避免拼接不可信字符串,并配合明确的输入验证和访问控制。
筛选输入时也要避免使用含糊的真值规则。若业务要求某字段必须是正整数,单纯使用 filter(None, values) 会保留字符串等其他真值,也会丢弃零;它既没有验证类型,也没有完整验证业务范围。应根据实际数据结构编写明确的检查逻辑,并在需要时报告无效数据,而不是无声丢弃。
总结:先看要什么结果,再选写法
可以用下面几条规则快速决定:
- 需要立即得到可索引、可重复遍历的列表:用列表推导式,或将
filter()显式转换为list。 - 只保留原元素,且已有清楚、可复用的判断函数:可以使用
filter()。 - 需要边筛选边转换:优先考虑列表推导式;若要惰性处理,可组合
filter()、map()或生成器表达式。 - 只排除
None、空字符串等特定值:写出具体条件,不要误用filter(None, iterable)。 - 数据量大或性能敏感:先确定是否需要保存全部结果,再用真实场景测量,不要只凭语法判断性能。
简而言之,filter() 更像“把满足条件的元素筛出来”,返回一个可逐步消费的迭代器;列表推导式则可以把遍历、筛选和生成新结果写在一起,并立即得到列表。理解这两个差异后,选择哪一种就不再是语法偏好问题,而是根据数据规模、结果用途和代码可读性做出的实际判断。




