【问题标题】:Why doesn't this list comprehension do what I expect it to do?为什么这个列表理解不符合我的预期?
【发布时间】:2010-05-30 19:53:08
【问题描述】:

project_keys = sorted(projects.keys())的原始列表是[101, 102, 103, 104, 105, 106, 107, 108, 109, 110],其中以下项目今年被视为invalid:108、109、110。

因此:

for project in projects.itervalues():
# The projects dictionary is mapped to the Project class
    if project.invalid:
    # Where invalid is a Bool parameter in the Project class
     project_keys.remove(project.proj_id)  

print project_keys

这将返回一个整数列表(即项目 ID):

[101, 102, 103, 104, 105, 106, 107]

甜。

现在,我希望它使用列表推导式尝试同样的事情。

project_keys = [project_keys.remove(project.proj_id) for project in projects.itervalues() if project.invalid  

print project_keys

这会返回:

[None, None, None]

所以我要填充一个与已删除元素相同编号的列表,但它们是Nones?

谁能指出我做错了什么?

此外,我为什么要在顶部的 for-if 块上使用列表推导?简明?好看吗?

【问题讨论】:

  • 嗯,理解显然有效,但显然不如预期。标题已更新。
  • @Az,以及那些认为我不应该提供相当于 RTFM 的答案的人:我建议更多地咨询文档的原因是我看到你最近一直在努力工作。这并没有错,但是虽然您的一些问题是真实的,但“我该如何解决这个问题?” (好),其他人在我看来更像是“我不想查这个;你们能告诉我吗?” (在我看来,不太好)。我没有恶意;尽管有些人可能会这么想,但我不是反对新手,只是支持文档。
  • @John Y:时间有点压力。此外,Python 文档——虽然对我理解良好或已广泛使用的部分很有用——对于初学者来说仍然是相当迟钝的。我知道我需要转向解决方案的构建块,但有时我发现文档模糊了我的路径。你明白我的意思吗?我理解你的担心,都一样。此外,stackoverflow.com 的成员向我展示了一些非常有趣的方法来处理问题,这是 learning 的经验。我支持学习;)

标签: python list-comprehension


【解决方案1】:

您的列表理解使用副作用。只需执行它应该更新 project_keys 以提供您想要的结果。

[project_keys.remove(project.proj_id)
 for project in projects.itervalues()
 if project.invalid]

remove 的返回值为None。将列表理解的结果分配给 project_keys 是您出错的地方。

一个简单的循环在这里可能更清晰。使用副作用的列表推导可能会造成混淆。

但是,您可以通过稍微不同的方式解决您的问题:

project_keys = sorted(project.proj_id
                      for project in projects.itervalues()
                      if not project.invalid)

这会保留您感兴趣的项目,而不是删除您不感兴趣的项目。我上面给出的示例使用生成器表达式而不是列表推导式,但两者都适用。

【讨论】:

  • 看起来你比 Dirk 快了 12 秒,但他是一个真正的列表理解,而你的是(在我写这篇文章时,也许你稍后会编辑它)严格来说是生成器表达式而不是列表理解。
  • @John Y:是的。 :) 我会考虑修复它的最佳方法。谢谢。
  • 不错的编辑。显然,德克认为你的更全面的解释使他的解释变得多余,而我自己的解释信息量少且过于刻薄,所以我的解释也很快就会消失。
  • Marks 的第二个解决方案——制作列表的副本——比在迭代列表时使用 remove() 好得多。在迭代其成员的循环内修改集合是有风险的。它可能会按预期工作或灾难性地失败,具体取决于列表和迭代器类的实现。即使在list s 上进行就地修改,如果有人为您的代码提供不同类型的集合会发生什么?还是 Python 的 list 在下一版本的实现变化?而复制策略总是有效的,即使是在不可变的集合上,比如元组
  • @Dan He 在从 project_keys 删除时迭代 projects.itervalues(),所以不,他没有修改他正在迭代的内容。
【解决方案2】:

先生,您误解了列表理解。

你可能想要什么(用文字)

我想删除所有无效的项目 ID。

你写了什么

project_keys = [project_keys.remove(project.proj_id)
                for project in projects.itervalues() if project.invalid]

实际发生了什么

dummy = []
for project in projects.itervalues():
  if project.invalid:
    dummy.append(project_keys.remove(project.proj_id)) #what are you
project_keys = dummy                                   #removing items from?
del dummy                                            

实际发生了什么(现在有了更多“功能”)

mapped-fun = lambda project: project_keys.remove(project.proj_id)
filtering-fun = lambda project: project.invalid
project_keys = map(mapped-fun, filter(filtering-fun, projects.itervalues()))

如您所见,列表推导式不是围绕for 循环的语法糖。相反,列表推导是 map()filter() 周围的语法糖:将函数应用于 sequence 中匹配条件的所有项目并获得 列表 个结果作为回报。

这里,函数实际上是指输入到输出的无副作用转换。这意味着您“不能”使用更改输入本身的方法,例如list.sort();您必须使用它们的等效功能,例如 sorted()

但是,“不能”并不是说您会收到错误消息或nasal demons;我的意思是你在滥用语言。在您的情况下,在将列表理解分配给变量时对其进行评估确实会产生预期的副作用 - 但它是否会在预期的变量上产生它们?

看,这可以在没有错误的情况下执行的唯一原因是在此列表理解之前,有 另一个 列表称为 project_keys 并且它是 那个 您所在的列表真的变了!

列表推导是函数式编程的结果,它拒绝副作用。使用列表推导时请记住这一点。


因此,您可以使用以下思考过程来实际获得所需的列表理解。

你真正想要的(用文字)

我想要所有有效的项目 ID(= 不是无效的。)

你真正想要的

dummy = []
for project in projects.itervalues():
  if not project.invalid:
    dummy.append(project.proj_id)
project_keys = dummy
del dummy

你真正想要的(现在有更多功能)

mapped-fun = lambda project: project.proj_id
filtering-fun = lambda project: not project.invalid
project_keys = map(mapped-fun, filter(filtering-fun, projects.itervalues()))

你真正想要的(现在作为列表理解)

project_keys = [project.proj_id for project in projects.itervalues()
                if not project.invalid]

【讨论】:

  • 老兄,这真是太棒了:)! Mark Ba​​yers 的一个有效,所以我检查了它作为一个可行的答案(它非常好,而且它更早存在)。非常感谢您抽出时间详细说明。
猜你喜欢
  • 2015-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-07
相关资源
最近更新 更多