【问题标题】:Filter elements from a list instance (or object) in a single expression在单个表达式中过滤列表实例(或对象)中的元素
【发布时间】:2020-07-17 06:48:33
【问题描述】:

在使用os.walk 时需要我更改生成的目录列表以过滤它们,我想知道是否有一个很好的方法来做到这一点:

for path, dirs, files in os.walk("."):
    for excl in EXCLUDES:
        if excl in dirs:
            dirs.remove(excl)

我知道我可以像[e for e in dirs if e not in EXCLUDES] 一样使用generate a new dirs with list comprehension,但是当我需要修改原始实例时这将不起作用。

写作……像 [dirs.remove(e) for e in dirs if e in EXCLUDES] 确实有效 但我在滥用列表理解 - pylint 不喜欢它(出于某种原因),未来版本的 Python 甚至可能会省略这个表达式,因为不使用生成的值。

所以在提供的场景中 - 有没有办法在列表上进行操作

dirs.remove_items(e for e in EXCLUDES if e in dirs)

或更一般地说:是否有(推荐的)方法以“for each”语义运行命令,例如

fn(e) for e in container

【问题讨论】:

    标签: python-3.x list list-comprehension


    【解决方案1】:

    您可以使用list 理解来做到这一点,您只需使用空切片分配来替换原始dirs内容

    dirs[:] = [e for e in dirs if e not in EXCLUDES]
    #   ^^^ Replaces contents       ^^^ flip condition so you only keep not-excluded
    

    这不违反任何正确使用 listcomp 的规则; listcomp 没有副作用,它只是生成一个新的list,然后您可以用它来批量替换dirs 的原始内容。

    除此之外,对于您的问题,“是否有一种(推荐的)方法可以在“for each”语义中运行命令,这是一个 for 循环,就像您已经写的那样:

     for x in iterable:
         fn(x)
    

    【讨论】:

      【解决方案2】:

      所以在提供的场景中 - 有没有办法对列表进行操作,例如 dirs.remove_items(e for e in EXCLUDES if e in dirs)

      没有。列表没有内置的 remove_itemsremove_all 函数。对于集合,您可以使用 intersection_update-= 来完成这项工作:

      >>> EXCLUDES = {"a", "b", "c"}
      >>> dirs = {"a", "x", "y"}
      >>> dirs -= EXCLUDES
      >>> dirs == {'x', 'y'}
      True
      

      或者更笼统地说:是否有(推荐的)方式以“for each”语义运行命令,例如 fn(e) for e in container

      是的:如果fn(e) 是纯的,则使用列表推导式,否则,编写:

      for e in container:
          fn(e)
      

      一些说明

      编辑下面的@ShadowRanger 评论。

      doc 声明:

      当 topdown 为 True 时,调用者可以就地修改 dirnames 列表(可能使用 del 或 slice 赋值),并且 walk() 只会递归到名称保留在 dirnames 中的子目录;这可用于修剪搜索,强制执行特定的访问顺序,甚至在调用者再次恢复 walk() 之前通知 walk() 有关调用者创建或重命名的目录。当 topdown 为 False 时修改 dirnames 对 walk 的行为没有影响,因为在自底向上模式下,dirnames 中的目录是在 dirpath 本身生成之前生成的。

      我不明白 OP 的目标是修剪搜索。以下评论不适用于这种情况,但总体上仍然适用。请参阅@ShadowRanger 的答案以使用切片分配修剪搜索。

      编辑结束

      正如您所说,使用列表推导(或map)产生副作用是个坏主意。列表推导返回一个列表,句号。

      但是没有什么能阻止你将你写的列表理解分配给 dirs 本身:

      dirs = [e for e in dirs if e not in EXCLUDES]
      

      您不是在删除项目,而是在创建一个新列表(如果我没记错的话,这就是列表理解的重点)。如果您担心创建新列表的成本,可以使用生成器:

      >>> EXCLUDES = {"a", "b", "c"}
      >>> dirs = ["a", "x", "y"]
      >>> dirs = (e for e in dirs if e not in EXCLUDES)
      >>> dirs
      <generator object <genexpr> at ...>
      >>> list(dirs)
      ['x', 'y']
      

      生成器惰性,因此将从dirs 读取元素并即时过滤。不会创建新列表。 filter 也是如此:

      >>> dirs = filter(lambda f: f not in EXCLUDES, dirs)
      >>> dirs
      <filter object at ...>
      >>> list(dirs)
      ['x', 'y']
      

      但不适用于reduce

      >>> from functools import reduce
      >>> dirs = reduce(lambda acc, f: acc if f in EXCLUDES else acc + [f], dirs, [])
      >>> dirs
      ['x', 'y']
      

      【讨论】:

      • “但是没有什么能阻止你将你写的列表理解分配给 dirs 本身” 实际上,在这种情况下,这是行不通的;由os.walk 生成的dirs 必须就地修改以影响下一个循环中所走的内容。将dirs 重新绑定到全新list 的结果不会更改隐藏在walk 中的list(它在dirs 的初始绑定中提供了别名)。您不能使用dirs = [e for e in dirs if e not in EXCLUDES] 来修改原始的list,但您可以使用切片分配来执行此操作,如我的回答中所述。
      • @ShadowRanger 你是对的,但问题不是很清楚。 “更改生成的目录列表以过滤它们”并不完全意味着修剪 os.walk 树。我将编辑我的答案。
      猜你喜欢
      • 1970-01-01
      • 2017-12-31
      • 1970-01-01
      • 2020-02-11
      • 1970-01-01
      • 1970-01-01
      • 2014-06-03
      • 2018-08-27
      • 1970-01-01
      相关资源
      最近更新 更多