首页 文章

列表理解与 Map

提问于
浏览
583

是否有理由更喜欢使用 map() 而不是列表理解,反之亦然?它们中的任何一个通常比另一个更有效或被认为是更加pythonic?

9 回答

  • 5

    map 在某些情况下可能在显微镜下更快(当你没有为此目的制作lambda,但在map和listcomp中使用相同的函数时) . 在其他情况下,列表推导可能更快,并且大多数(并非所有)pythonistas认为它们更直接和更清晰 .

    使用完全相同的功能时, Map 的微小速度优势的一个例子:

    $ python -mtimeit -s'xs=range(10)' 'map(hex, xs)'
    100000 loops, best of 3: 4.86 usec per loop
    $ python -mtimeit -s'xs=range(10)' '[hex(x) for x in xs]'
    100000 loops, best of 3: 5.58 usec per loop
    

    当map需要lambda时,如何完全颠倒性能比较的示例:

    $ python -mtimeit -s'xs=range(10)' 'map(lambda x: x+2, xs)'
    100000 loops, best of 3: 4.24 usec per loop
    $ python -mtimeit -s'xs=range(10)' '[x+2 for x in xs]'
    100000 loops, best of 3: 2.32 usec per loop
    
  • 15

    Cases

    • Common case :几乎总是,你会想在python中使用列表推导,因为你对python程序员所做的事情会更加明显,因为列表推导是python中迭代的事实上的标准;他们是预期的 .

    • Less-common case :但是如果您已经定义了一个函数,那么使用 map 通常是合理的,尽管它被认为是'unpythonic' . 例如, map(sum, myLists)[sum(x) for x in myLists] 更优雅/简洁 . 您可以获得不需要组成虚拟变量(例如 sum(x) for x...sum(_) for _...sum(readableName) for readableName... )的优雅,您必须输入两次,只是为了迭代 . filterreduce 以及 itertools 模块中的任何内容都有相同的参数:如果您已经有一个方便的函数,您可以继续进行一些函数式编程 . 这在某些情况下会增加可读性,在其他情况下会丢失(例如,新手程序员,多个参数)......但是代码的可读性在很大程度上取决于您的评论 .

    • Almost never :您可能希望在进行函数式编程时使用 map 函数作为纯抽象函数,您可以映射 map ,或者使用 map ,或以其他方式将 map 作为函数进行讨论 . 例如,在Haskell中,一个名为 fmap 的仿函数接口概括了任何数据结构的映射 . 这在python中非常罕见,因为python语法迫使你使用生成器风格来讨论迭代;你不能轻易地概括它 . (这有时是好的,有时也很糟糕 . )你可能会想出一些罕见的python例子,其中 map(f, *lists) 是一个合理的事情 . 我能想出的最接近的例子是 sumEach = partial(map,sum) ,这是一个非常大致相当于以下内容的单线程:

    def sumEach(myLists):
        return [sum(_) for _ in myLists]
    
    • Just using a for-loop :您当然也可以使用for循环 . 虽然从功能编程的角度来看并不那么优雅,但有时非局部变量会使命令式编程语言(如python)中的代码更加清晰,因为人们习惯于以这种方式阅读代码 . 当你只是进行任何复杂的操作时,for循环通常也是最有效的,这些操作没有构建像list-comprehensions这样的列表,并且map被优化(例如求和,或者做树等) - 至少在记忆方面有效(不一定是在时间方面,我认为在最坏的情况下是一个恒定的因素,除非一些罕见的病态垃圾收集打嗝) .

    "Pythonism"

    我不喜欢"pythonic"这个词,因为我没有发现pythonic在我眼中总是优雅的 . 尽管如此, mapfilter 以及类似的功能(如非常有用的 itertools 模块)在风格方面可能被认为是非语音的 .

    Laziness

    就效率而言,就像大多数函数式编程结构一样,_实际上在python中是懒惰的 . 这意味着您可以执行此操作(在python3中),并且您的计算机不会耗尽内存并丢失所有未保存的数据:

    >>> map(str, range(10**100))
    <map object at 0x2201d50>
    

    尝试使用列表理解:

    >>> [str(n) for n in range(10**100)]
    # DO NOT TRY THIS AT HOME OR YOU WILL BE SAD #
    

    请注意,列表推导也本质上是懒惰的,但是python选择将它们实现为非惰性 . 尽管如此,python确实以生成器表达式的形式支持惰性列表推导,如下所示:

    >>> (str(n) for n in range(10**100))
    <generator object <genexpr> at 0xacbdef>
    

    您基本上可以将 [...] 语法视为将生成器表达式传递给列表构造函数,如 list(x for x in range(5)) .

    Brief contrived example

    from operator import neg
    print({x:x**2 for x in map(neg,range(5))})
    
    print({x:x**2 for x in [-y for y in range(5)]})
    
    print({x:x**2 for x in (-y for y in range(5))})
    

    列表推导是非惰性的,因此可能需要更多内存(除非您使用生成器理解) . 方括号 [...] 经常使事情变得明显,尤其是在括号中 . 另一方面,有时你最终会像输入 [x for x in... 一样冗长 . 只要你保留你的迭代器变量简短,如果不缩进代码,列表推导通常会更清晰 . 但是你总是可以缩进你的代码 .

    print(
        {x:x**2 for x in (-y for y in range(5))}
    )
    

    或打破局面:

    rangeNeg5 = (-y for y in range(5))
    print(
        {x:x**2 for x in rangeNeg5}
    )
    

    Efficiency comparison for python3

    map 现在很懒:

    % python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=map(f,xs)'
    1000000 loops, best of 3: 0.336 usec per loop            ^^^^^^^^^
    

    因此,如果您不使用所有数据,或者不知道您需要多少数据,python3中的 map (以及python2或python3中的生成器表达式)将避免在必要的最后时刻之前计算它们的值 . 通常这通常会超过使用 map 的任何开销 . 缺点是python与大多数函数式语言相比非常有限:如果从左到右访问数据,你只能获得这个好处,因为python生成器表达式只能按照 x[0], x[1], x[2], ... 的顺序进行求值 .

    但是,假设我们有一个预先设定的函数 f 我们想 map ,我们通过立即强制使用 list(...) 进行评估来忽略 map 的懒惰 . 我们得到一些非常有趣的结果:

    % python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(map(f,xs))'                                                                                                                                                
    10000 loops, best of 3: 165/124/135 usec per loop        ^^^^^^^^^^^^^^^
                        for list(<map object>)
    
    % python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=[f(x) for x in xs]'                                                                                                                                      
    10000 loops, best of 3: 181/118/123 usec per loop        ^^^^^^^^^^^^^^^^^^
                        for list(<generator>), probably optimized
    
    % python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(f(x) for x in xs)'                                                                                                                                    
    1000 loops, best of 3: 215/150/150 usec per loop         ^^^^^^^^^^^^^^^^^^^^^^
                        for list(<generator>)
    

    结果是AAA / BBB / CCC形式,其中A是在大约2010年英特尔工作站上使用python 3执行的 . ? . ?,B和C是使用带有python 3.2.1的大约2013 AMD工作站执行的,硬件极其不同 . 结果似乎是 Map 和列表推导在性能方面具有可比性,受其他随机因素的影响最大 . 奇怪的是,我们唯一可以告诉的是,虽然我们希望列表推导 [...] 比生成器表达式 (...) 更好,但是 map 也比生成器表达式更有效(再次假设所有值都被评估/使用) .

    重要的是要意识到这些测试假设一个非常简单的功能(身份功能);但这很好,因为如果功能很复杂,那么与程序中的其他因素相比,性能开销可以忽略不计 . (用 f=lambda x:x+x 等其他简单的东西测试可能仍然很有趣)

    如果你擅长阅读python程序集,你可以使用 dis 模块来查看's actually what'是否在幕后进行:

    >>> listComp = compile('[f(x) for x in xs]', 'listComp', 'eval')
    >>> dis.dis(listComp)
      1           0 LOAD_CONST               0 (<code object <listcomp> at 0x2511a48, file "listComp", line 1>) 
                  3 MAKE_FUNCTION            0 
                  6 LOAD_NAME                0 (xs) 
                  9 GET_ITER             
                 10 CALL_FUNCTION            1 
                 13 RETURN_VALUE         
    >>> listComp.co_consts
    (<code object <listcomp> at 0x2511a48, file "listComp", line 1>,)
    >>> dis.dis(listComp.co_consts[0])
      1           0 BUILD_LIST               0 
                  3 LOAD_FAST                0 (.0) 
            >>    6 FOR_ITER                18 (to 27) 
                  9 STORE_FAST               1 (x) 
                 12 LOAD_GLOBAL              0 (f) 
                 15 LOAD_FAST                1 (x) 
                 18 CALL_FUNCTION            1 
                 21 LIST_APPEND              2 
                 24 JUMP_ABSOLUTE            6 
            >>   27 RETURN_VALUE
    
    >>> listComp2 = compile('list(f(x) for x in xs)', 'listComp2', 'eval')
    >>> dis.dis(listComp2)
      1           0 LOAD_NAME                0 (list) 
                  3 LOAD_CONST               0 (<code object <genexpr> at 0x255bc68, file "listComp2", line 1>) 
                  6 MAKE_FUNCTION            0 
                  9 LOAD_NAME                1 (xs) 
                 12 GET_ITER             
                 13 CALL_FUNCTION            1 
                 16 CALL_FUNCTION            1 
                 19 RETURN_VALUE         
    >>> listComp2.co_consts
    (<code object <genexpr> at 0x255bc68, file "listComp2", line 1>,)
    >>> dis.dis(listComp2.co_consts[0])
      1           0 LOAD_FAST                0 (.0) 
            >>    3 FOR_ITER                17 (to 23) 
                  6 STORE_FAST               1 (x) 
                  9 LOAD_GLOBAL              0 (f) 
                 12 LOAD_FAST                1 (x) 
                 15 CALL_FUNCTION            1 
                 18 YIELD_VALUE          
                 19 POP_TOP              
                 20 JUMP_ABSOLUTE            3 
            >>   23 LOAD_CONST               0 (None) 
                 26 RETURN_VALUE
    
    >>> evalledMap = compile('list(map(f,xs))', 'evalledMap', 'eval')
    >>> dis.dis(evalledMap)
      1           0 LOAD_NAME                0 (list) 
                  3 LOAD_NAME                1 (map) 
                  6 LOAD_NAME                2 (f) 
                  9 LOAD_NAME                3 (xs) 
                 12 CALL_FUNCTION            2 
                 15 CALL_FUNCTION            1 
                 18 RETURN_VALUE
    

    似乎使用 [...] 语法比 list(...) 更好 . 可悲的是 map 类对于反汇编来说有点不透明,但我们可以通过速度测试来实现 .

  • 37

    您应该使用 Map 和过滤器而不是列表推导 .

    objective 为什么你应该更喜欢它们,即使它们不是"Pythonic"是这样的:
    它们需要函数/ lambdas作为参数, introduce a new scope .

    我不止一次被这个咬过了:

    for x, y in somePoints:
        # (several lines of code here)
        squared = [x ** 2 for x in numbers]
        # Oops, x was silently overwritten!
    

    但如果相反我说:

    for x, y in somePoints:
        # (several lines of code here)
        squared = map(lambda x: x ** 2, numbers)
    

    那么一切都会好起来的 .

    你可以说我在同一范围内使用相同的变量名称是愚蠢的 .

    我不是 . 代码原本很好 - 两个 x 不在同一范围内 .
    只有在问题出现后,代码的不同部分的内部块才会出现(读取:维护期间出现问题,而不是开发),我没想到 .

    是的,如果你从未犯过这个错误,那么列表理解就更优雅了 .
    但是从个人经验(以及看到其他人犯了同样的错误)来看,当这些错误蔓延到你的代码中时,我不值得你经历的痛苦 .

    结论:

    使用 mapfilter . 它们可以防止难以诊断的与范围相关的细微错误 .

    附注:

    如果它们适合您的情况,请不要忘记考虑使用 imapifilter (在 itertools 中)!

  • 13

    实际上, map 和列表推导在Python 3语言中的表现完全不同 . 看看下面的Python 3程序:

    def square(x):
        return x*x
    squares = map(square, [1, 2, 3])
    print(list(squares))
    print(list(squares))
    

    您可能希望它打印行"[1, 4, 9]"两次,但它打印"[1, 4, 9]"后跟"[]" . 它第一次看 squares 它似乎表现为三个元素的序列,但第二次作为空元素 .

    在Python 2语言中, map 返回一个普通的旧列表,就像列表推导在两种语言中一样 . 关键是Python 3中的 map (以及Python 2中的 imap )的返回值不是列表 - 它是一个迭代器!

    迭代迭代器时会消耗元素,这与迭代列表时不同 . 这就是 squares 在最后 print(list(squares)) 行中看起来为空的原因 .

    总结一下:

    • When dealing with iterators you have to remember that they are stateful and that they mutate as you traverse them.

    • 列表更具可预测性,因为它们只会在您改变时发生变化明确地改变他们;它们是容器 .

    • 还有一个好处:数字,字符串和元组更加可预测,因为它们根本无法改变;他们是 Value 观 .

  • 0

    我发现列表推导通常比我正在尝试做的更具表现力 - 他们都完成了它,但是前者节省了试图理解什么可能是复杂的 lambda 表达式的心理负担 .

    在那里,Guido列出了 lambda 和函数函数作为他最接近Python的东西,所以你可以通过它来证明他们是非Pythonic的 .

  • 80

    这是一个可能的情况:

    map(lambda op1,op2: op1*op2, list1, list2)
    

    与:

    [op1*op2 for op1,op2 in zip(list1,list2)]
    

    我猜你的zip()是一个不幸和不必要的开销,如果你坚持使用列表推导而不是 Map ,你需要沉迷 . 如果有人澄清这一点是肯定的还是消极的,那将是很好的 .

  • 386

    如果您计划编写任何异步,并行或分布式代码,您可能更喜欢 map 而不是列表理解 - 因为大多数异步,并行或分布式软件包提供 map 函数来重载python的 map . 然后通过将适当的 map 函数传递给其余代码,您可能不必修改原始序列代码以使其并行运行(等) .

  • 13

    因此,自Python 3起,map()是一个迭代器,你需要记住你需要什么:迭代器或 list 对象 .

    由于@AlexMartelli已经mentionedmap() 仅在不使用 lambda 函数时比列表理解快 .

    我会告诉你一些时间比较 .

    Python 3.5.2和CPython我使用过Jupiter笔记本,特别是%timeit内置魔术命令测量:s == 1000 ms == 1000 *1000μs= 1000 * 1000 * 1000 ns

    Build :

    x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)]
    i_list = list(range(1000))
    

    内置功能:

    %timeit map(sum, x_list)  # creating iterator object
    # Output: The slowest run took 9.91 times longer than the fastest. 
    # This could mean that an intermediate result is being cached.
    # 1000000 loops, best of 3: 277 ns per loop
    
    %timeit list(map(sum, x_list))  # creating list with map
    # Output: 1000 loops, best of 3: 214 µs per loop
    
    %timeit [sum(x) for x in x_list]  # creating list with list comprehension
    # Output: 1000 loops, best of 3: 290 µs per loop
    

    lambda 功能:

    %timeit map(lambda i: i+1, i_list)
    # Output: The slowest run took 8.64 times longer than the fastest. 
    # This could mean that an intermediate result is being cached.
    # 1000000 loops, best of 3: 325 ns per loop
    
    %timeit list(map(lambda i: i+1, i_list))
    # Output: 1000 loops, best of 3: 183 µs per loop
    
    %timeit [i+1 for i in i_list]
    # Output: 10000 loops, best of 3: 84.2 µs per loop
    

    还有生成器表达式这样的东西,参见PEP-0289 . 所以我认为将它添加到比较中会很有用

    %timeit (sum(i) for i in x_list)
    # Output: The slowest run took 6.66 times longer than the fastest. 
    # This could mean that an intermediate result is being cached.
    # 1000000 loops, best of 3: 495 ns per loop
    
    %timeit list((sum(x) for x in x_list))
    # Output: 1000 loops, best of 3: 319 µs per loop
    
    %timeit (i+1 for i in i_list)
    # Output: The slowest run took 6.83 times longer than the fastest. 
    # This could mean that an intermediate result is being cached.
    # 1000000 loops, best of 3: 506 ns per loop
    
    %timeit list((i+1 for i in i_list))
    # Output: 10000 loops, best of 3: 125 µs per loop
    

    您需要列表对象:

    如果是自定义函数,请使用列表推导,如果有内置函数,请使用 list(map())

    您不需要列表对象,只需要可迭代的对象:

    始终使用 map()

  • 541

    我认为最Pythonic的方法是使用列表理解而不是 mapfilter . 原因是列表理解比 mapfilter 更清晰 .

    In [1]: odd_cubes = [x ** 3 for x in range(10) if x % 2 == 1] # using a list comprehension
    
    In [2]: odd_cubes_alt = list(map(lambda x: x ** 3, filter(lambda x: x % 2 == 1, range(10)))) # using map and filter
    
    In [3]: odd_cubes == odd_cubes_alt
    Out[3]: True
    

    如您所见,理解不需要额外的 lambda 表达式作为 map 需要 . 此外,理解还允许轻松过滤,而 map 需要 filter 以允许过滤 .

相关问题