Python内置方法过滤器在for循环中的神秘行为



考虑以下事实:

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]

所以35都没有被移除,但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]

我可以感觉到这是由于过滤器对象是一个生成器,但无法准确解释生成器如何导致这种一致的奇怪行为,请帮助详细说明潜在的合理性。

该行为源于两个事实的组合:

  1. 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
  1. 因为filter返回一个生成器,所以当你实际迭代它时,而不是在调用filter时,会延迟计算该函数。

在第一个示例中,这些内容的综合效果是,当您迭代filter对象时,i的值为 9,并且此值用于 lambda 函数。

通过消除上述两个组合因素中的一个(或两个(,可以获得所需的行为:

  1. 在 lambda 中,通过创建闭包来强制早期绑定,其中使用i的值作为参数的默认值(例如j(,因此代替lambda x: x != i,您将使用:
lambda x, j=i: x != j
  • 默认值的表达式(即 定义 lambda 时计算i(,并且通过仅使用一个参数 (x( 调用 lambda,这可以确保您不会在执行时覆盖此默认值。

或:

  1. 通过立即转换为列表来强制提前执行生成器的所有迭代(正如您所观察到的(。

最新更新