考虑以下事实:
a = list(range(10))
res = list(a)
for i in a:
if i in {3, 5}:
print('>>>', i)
res = filter(lambda x: x != i, res)
print(list(res))
>>> 3
>>> 5
[0, 1, 2, 3, 4, 5, 6, 7, 8]
所以3
和5
都没有被移除,但9
已经消失了......
如果我强制将过滤器对象转换为列表,那么它按预期工作:
a = list(range(10))
res = list(a)
for i in a:
if i in {3, 5}:
print('>>>', i)
# Here i force to convert filter object to list then it will work as expected.
res = list(filter(lambda x: x != i, res))
print(list(res))
>>> 3
>>> 5
[0, 1, 2, 4, 6, 7, 8, 9]
我可以感觉到这是由于过滤器对象是一个生成器,但无法准确解释生成器如何导致这种一致的奇怪行为,请帮助详细说明潜在的合理性。
该行为源于两个事实的组合:
- lambda 函数包含从周围作用域获取的变量
i
,该变量仅在执行时计算。 请考虑以下示例:
>>> func = lambda x: x != i # i does not even need to exist yet
>>> i = 3
>>> func(3) # now i will be used
False
- 因为
filter
返回一个生成器,所以当你实际迭代它时,而不是在调用filter
时,会延迟计算该函数。
在第一个示例中,这些内容的综合效果是,当您迭代filter
对象时,i
的值为 9,并且此值用于 lambda 函数。
通过消除上述两个组合因素中的一个(或两个(,可以获得所需的行为:
- 在 lambda 中,通过创建闭包来强制早期绑定,其中使用
i
的值作为参数的默认值(例如j
(,因此代替lambda x: x != i
,您将使用:
lambda x, j=i: x != j
- 默认值的表达式(即 定义 lambda 时计算
i
(,并且通过仅使用一个参数 (x
( 调用 lambda,这可以确保您不会在执行时覆盖此默认值。
或:
- 通过立即转换为列表来强制提前执行生成器的所有迭代(正如您所观察到的(。