代码之家  ›  专栏  ›  技术社区  ›  Agos

列表理解与lambda+过滤器

  •  712
  • Agos  · 技术社区  · 14 年前

    我碰巧发现自己有一个基本的过滤需求:我有一个列表,我必须按项目的属性过滤它。

    我的代码如下:

    my_list = [x for x in my_list if x.attribute == value]
    

    但后来我想,这样写不是更好吗?

    my_list = filter(lambda x: x.attribute == value, my_list)
    

    它的可读性更强,如果性能需要,lambda可以被取出以获得一些东西。

    问题是:使用第二种方法有什么注意事项吗?性能有什么不同吗?我是否完全错过了蟒蛇式的方式,应该用另一种方式来完成它(例如使用itemgetter而不是lambda)?

    14 回复  |  直到 6 年前
        1
  •  490
  •   Duncan    6 年前

    奇怪的是,对于不同的人来说,美的差异有多大。我发现清单的理解比 filter + lambda 但要用你觉得容易的。但是,不要给已经用于内置的变量命名,这会让人困惑。[ 最初使用的问题 list 作为变量名,但已根据此答案更新。 ]

    有两件事可能会减慢你使用 滤波器 .

    第一个是函数调用开销:只要使用python函数(无论 def 兰姆达 )过滤器可能会比列表理解慢。几乎可以肯定,这并不重要,在对代码进行计时并发现它是一个瓶颈之前,您不应该过多地考虑性能,但两者之间的区别将是存在的。

    另一个可能应用的开销是lambda被强制访问一个作用域变量( value )这比访问局部变量慢,在python 2.x中,列表理解只访问局部变量。如果您使用的是python 3.x,那么列表理解将在单独的函数中运行,因此它也将访问 价值 通过一个闭包,这种差异将不适用。

    另一个要考虑的选择是使用生成器而不是列表理解:

    def filterbyvalue(seq, value):
       for el in seq:
           if el.attribute==value: yield el
    

    然后,在您的主代码(可读性真正重要的地方)中,您已经用一个希望有意义的函数名替换了列表理解和过滤器。

        2
  •  204
  •   Tendayi Mawushe    14 年前

    这在巨蟒身上是一个宗教问题。尽管 Guido considered removing map , filter and reduce from Python 3 有足够的反弹,最终只有 减少 已从内置移动到 functools.reduce .

    就我个人而言,我觉得列表理解更容易阅读。从表达式中可以更清楚地看到正在发生的事情 [i for i in list if i.attribute == value] 因为所有的行为都在表面上,而不是在过滤功能内部。

    我不会太担心这两种方法之间的性能差异,因为它是边际的。如果它被证明是您的应用程序中的瓶颈(这不太可能),那么我真的只会对此进行优化。

    也因为 BDFL 需要的 滤波器 那么肯定是从语言中消失了,这会自动使列表理解更像是蟒蛇;-)

        3
  •  61
  •   Mateen Ulhaq    8 年前

    因为任何速度差都是很小的,所以不管是使用过滤器还是列表理解,归根结底都取决于品味。一般来说,我倾向于使用理解(这似乎与这里的大多数其他答案一致),但有一种情况我更喜欢 filter .

    一个非常常见的用例是根据谓词p(x)提取一些iterable x的值:

    [x for x in X if P(x)]
    

    但有时您希望首先对值应用一些函数:

    [f(x) for x in X if P(f(x))]
    


    作为一个具体的例子,考虑

    primes_cubed = [x*x*x for x in range(1000) if prime(x)]
    

    我觉得这个比用这个稍微好一点 滤波器 . 但现在考虑一下

    prime_cubes = [x*x*x for x in range(1000) if prime(x*x*x)]
    

    在这种情况下,我们想 滤波器 相对于后计算值。除了两次计算立方体的问题(想象一个更昂贵的计算),还有两次编写表达式的问题,违反了 DRY 审美。在这种情况下,我会倾向于使用

    prime_cubes = filter(prime, [x*x*x for x in range(1000)])
    
        4
  •  28
  •   Umang    14 年前

    虽然 filter 可能是“更快的方式”,“蟒蛇式的方式”不会关心这些事情,除非性能是绝对关键的(在这种情况下,您不会使用python!).

        5
  •  15
  •   Jim50    8 年前

    我想在python 3中,filter()实际上是一个迭代器对象,所以您必须将filter方法调用传递到list()才能构建筛选列表。所以在Python 2中:

    lst_a = range(25) #arbitrary list
    lst_b = [num for num in lst_a if num % 2 == 0]
    lst_c = filter(lambda num: num % 2 == 0, lst_a)
    

    列表b和c具有相同的值,并且在与filter()等效的时间内完成(x代表x,y代表x,if z)。但是,在3中,相同的代码将使列表C包含一个筛选对象,而不是一个筛选列表。要在3中生成相同的值:

    lst_a = range(25) #arbitrary list
    lst_b = [num for num in lst_a if num % 2 == 0]
    lst_c = list(filter(lambda num: num %2 == 0, lst_a))
    

    问题是list()将iterable作为其参数,并从该参数创建一个新的列表。结果是,在python 3中以这种方式使用filter,花费的时间比y if z中x的[x for x]方法长两倍,因为您必须迭代filter()的输出和原始列表。

        6
  •  10
  •   Adeynack    10 年前

    一个重要的区别是列表理解将返回 list 当过滤器返回 filter 你不能像 列表 (电话) len 在它上面,这不适用于 滤波器 )

    我自己的自学使我遇到了类似的问题。

    也就是说,如果有办法 列表 从A 滤波器 ,有点像在.NET中那样 lst.Where(i => i.something()).ToList() 我很想知道。

    编辑:这是Python3的情况,而不是2(参见注释中的讨论)。

        7
  •  9
  •   unbeli    14 年前

    我发现第二种方式更具可读性。它确切地告诉你目的是什么:过滤列表。
    ps:不要将“list”用作变量名

        8
  •  7
  •   thiruvenkadam    10 年前

    Filter 就是这样。它过滤掉列表中的元素。您可以看到定义提到相同的内容(在我前面提到的官方文档链接中)。然而,列表理解是在执行之后产生一个新列表的东西 某物 在上一个列表中。(筛选和列表理解都会创建新列表,而不会代替旧列表执行操作。这里的一个新列表类似于一个具有全新数据类型的列表。例如将整数转换为字符串等)

    在您的示例中,根据定义,使用过滤比列表理解更好。但是,如果您愿意,比如说列表元素中的其他_属性,在您的示例中是作为一个新列表来检索的,那么您可以使用列表理解。

    return [item.other_attribute for item in my_list if item.attribute==value]
    

    这就是我对过滤和列表理解的实际记忆。删除列表中的一些内容并保持其他元素的完整性,使用filter。在元素中单独使用一些逻辑,并创建一个适合某些目的的减淡列表,使用列表理解。

        9
  •  6
  •   John La Rooy    14 年前

    通常地 filter 如果使用内置函数,则速度稍快。

    在你的情况下,我希望单子理解得稍微快一点

        10
  •  5
  •   rharder    9 年前

    这是我用来过滤东西的短篇 之后 列表理解。只是过滤器、lambda和列表的组合(也称为猫的忠诚度和狗的清洁度)。

    在本例中,我正在读取一个文件,去掉空白行、注释行以及行上注释后的任何内容:

    # Throw out blank lines and comments
    with open('file.txt', 'r') as lines:        
        # From the inside out:
        #    [s.partition('#')[0].strip() for s in lines]... Throws out comments
        #   filter(lambda x: x!= '', [s.part... Filters out blank lines
        #  y for y in filter... Converts filter object to list
        file_contents = [y for y in filter(lambda x: x != '', [s.partition('#')[0].strip() for s in lines])]
    
        11
  •  4
  •   Nathaniel Ford iFail    6 年前

    除了接受的答案之外,还有一个角落案例,当您应该使用过滤器而不是列表理解时。如果列表不可显示,则不能直接用列表理解来处理它。一个现实世界的例子是如果你使用 pyodbc 从数据库中读取结果。这个 fetchAll() 结果来自 cursor 是一个不可删除的列表。在这种情况下,为了直接处理返回的结果,应该使用过滤器:

    cursor.execute("SELECT * FROM TABLE1;")
    data_from_db = cursor.fetchall()
    processed_data = filter(lambda s: 'abc' in s.field1 or s.StartTime >= start_date_time, data_from_db) 
    

    如果在这里使用列表理解,则会得到错误:

    类型错误:不可显示的类型:“list”

        12
  •  3
  •   user1767754    7 年前

    我花了一些时间熟悉 higher order functions filter map . 所以我习惯了他们,我真的很喜欢 滤波器 很明显,它是通过保持真实来过滤的,我觉得很酷,因为我知道一些 functional programming 条款。

    然后我读了这段话(流利的python书):

    map和filter函数仍然是内置的 在python 3中,但是由于引入了list理解和generator ex__ 压力,它们没有那么重要。listcomp或genexp执行映射和 过滤器组合,但可读性更强。

    现在我想,为什么还要考虑 滤波器 / 地图 如果你能用已经广泛传播的习语如清单理解来实现它的话。此外 maps filters 是一种功能。在这种情况下,我更喜欢使用 Anonymous functions 兰姆达斯

    最后,为了测试它,我对两种方法都进行了计时( 地图 listComp )我也没有看到任何相关的速度差异可以证明对它的争论是合理的。

    from timeit import Timer
    
    timeMap = Timer(lambda: list(map(lambda x: x*x, range(10**7))))
    print(timeMap.timeit(number=100))
    
    timeListComp = Timer(lambda:[(lambda x: x*x) for x in range(10**7)])
    print(timeListComp.timeit(number=100))
    
    #Map:                 166.95695265199174
    #List Comprehension   177.97208347299602
    
        13
  •  0
  •   Rod Senra    6 年前

    奇怪的是,在python 3上,我看到过滤器比列表理解执行得更快。

    我一直认为清单的理解会更有效。 类似: [品牌中名称的名称\如果名称不是“无”,则为“数据库”] 生成的字节码要好一点。

    >>> def f1(seq):
    ...     return list(filter(None, seq))
    >>> def f2(seq):
    ...     return [i for i in seq if i is not None]
    >>> disassemble(f1.__code__)
    2         0 LOAD_GLOBAL              0 (list)
              2 LOAD_GLOBAL              1 (filter)
              4 LOAD_CONST               0 (None)
              6 LOAD_FAST                0 (seq)
              8 CALL_FUNCTION            2
             10 CALL_FUNCTION            1
             12 RETURN_VALUE
    >>> disassemble(f2.__code__)
    2           0 LOAD_CONST               1 (<code object <listcomp> at 0x10cfcaa50, file "<stdin>", line 2>)
              2 LOAD_CONST               2 ('f2.<locals>.<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_FAST                0 (seq)
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE
    

    但实际上速度较慢:

       >>> timeit(stmt="f1(range(1000))", setup="from __main__ import f1,f2")
       21.177661532000116
       >>> timeit(stmt="f2(range(1000))", setup="from __main__ import f1,f2")
       42.233950221000214
    
        14
  •  -5
  •   tim    11 年前

    我的拿来

    def filter_list(list, key, value, limit=None):
        return [i for i in list if i[key] == value][:limit]