Python社区  »  Python

何时在python中使用map()[重复]

Derek Allums • 8 月前 • 62 次点击  

有没有理由更喜欢使用 map() 过度理解还是相反?它们中的任何一个通常比另一个更有效还是被认为更像蟒蛇?

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/42936
 
62 次点击  
分享到微博
文章 [ 9 ]  |  最新文章 8 月前
lmiguelvargasf
Reply   •   1 楼
lmiguelvargasf    2 年前

我认为最变态的方法是使用列表理解而不是 map filter . 原因是列表理解比 地图 滤波器 .

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 表达式为 地图 需要。此外,理解还可以方便地过滤,而 地图 要求 滤波器 允许过滤。

Community Tomalak
Reply   •   2 楼
Community Tomalak    3 年前

所以自从python 3之后, map() 是迭代器,您需要记住您需要什么:迭代器 list 对象。

已经是@alexmartelli了 mentioned , MAP() 如果你不使用 lambda 功能。

我会给你一些时间比较。

python 3.5.2和cpython
我已经用过 Jupiter notebook 尤其是 %timeit 内置魔术指令
测量 :s==1000ms==1000*1000s=1000*1000*1000ns

设置:

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

兰姆达 功能:

%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() !

Mike McKerns
Reply   •   3 楼
Mike McKerns    5 年前

如果您计划编写任何异步、并行或分布式代码,您可能会希望 map 对列表的理解——因为大多数异步、并行或分布式包提供了 地图 函数重载python的 地图 . 然后通过适当的 地图 函数到您的代码的其余部分,您可能不需要修改您的原始串行代码,使其并行运行(等)。

ninjagecko
Reply   •   4 楼
ninjagecko    8 年前

以下是一个可能的案例:

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

对比:

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

我猜如果坚持使用列表理解而不是映射,那么zip()是一个不幸的、不必要的开销。如果有人澄清这一点,无论是肯定的还是否定的。

Dan
Reply   •   5 楼
Dan    10 年前

我发现列表理解通常比 map -他们都完成了,但前者省去了试图理解什么可能是复杂的心理负担 lambda 表达式。

还有一个面试在某个地方(我不能马上找到),那里有guido列表 兰姆达 s和函数作为他最后悔接受到python中的东西,所以你可以认为它们是非pythonic的。

raek
Reply   •   6 楼
raek    6 年前

事实上, map 在python 3语言中,列表理解的行为非常不同。请看下面的Python3程序:

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语言中 地图 返回一个普通的旧列表,就像两种语言中的列表理解一样。关键在于 地图 在python 3中(和 imap 在Python2)中不是列表-它是迭代器!

元素在迭代器上迭代时使用,这与在列表上迭代时不同。这就是为什么 正方形 最后一次看起来是空的 print(list(squares)) 线。

总结一下:

  • 在处理迭代器时,必须记住它们是有状态的,并且在遍历它们时会发生变化。
  • 列表是更可预测的,因为它们只有在您显式地对它们进行变异时才会更改;它们是 容器 .
  • 还有一个好处:数字、字符串和元组更容易预测,因为它们根本无法更改;它们是 价值观 .
Paolo Moretti Mehrdad
Reply   •   7 楼
Paolo Moretti Mehrdad    5 年前

你应该用 map filter 而不是列表理解。

客观的 即使它们不是“蟒蛇”,你也应该喜欢它们的原因是:
它们需要函数/lambdas作为参数,而 引入新范围 .

我不止一次被这个咬过:

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 S不在同一范围内。
只是在我之后 感动 内部代码块到出现问题的代码的不同部分(读:维护期间的问题,而不是开发期间的问题),我没想到会这样。

对, 如果你从不犯这个错误 然后列表理解更优雅。
但从个人经验(以及看到其他人犯同样的错误)来看,我已经看到它发生了足够多的次数,我认为当这些错误潜入到代码中时,不值得你付出痛苦。

结论:

使用 地图 滤波器 . 它们可以防止与范围相关的细微的难以诊断的错误。

边注:

别忘了考虑使用 imap ifilter itertools )如果他们适合你的情况!

ninjagecko
Reply   •   8 楼
ninjagecko    2 年前

病例

  • 常见病例 :几乎总是,您需要在 蟒蛇 因为你对阅读你的代码的新手做的事情会更明显。(这不适用于其他语言,在这些语言中可能会用到其他习语。)您对python程序员所做的事情将更加明显,因为列表理解是python中用于迭代的事实上的标准;它们是 预期 .
  • 不太常见的情况 :但是,如果您 已经定义了函数 ,通常使用是合理的 map ,尽管它被认为是“不通顺的”。例如, map(sum, myLists) [sum(x) for x in myLists] . 不必构造伪变量(例如 sum(x) for x... sum(_) for _... sum(readableName) for readableName... )你必须输入两次,只是为了迭代。同样的论点适用于 filter reduce 任何来自 itertools 模块:如果你手头已经有一个函数,你可以继续做一些函数编程。这在某些情况下获得可读性,而在其他情况下(例如新手程序员、多个参数)则失去可读性。但是代码的可读性在很大程度上取决于您的注释。
  • 几乎从不 :您可能要使用 地图 函数作为一个纯抽象函数进行函数编程 地图 ,或咖喱 地图 ,或者从谈论 地图 作为一种功能。例如,在haskell中,一个名为 fmap 泛化任何数据结构上的映射。这在python中是非常少见的,因为python语法迫使您使用生成器样式来讨论迭代;您不能很容易地将其泛化。(这有时是好的,有时是坏的)您可能会想出一些罕见的python示例,其中 map(f, *lists) 是一件合理的事情。我能想到的最接近的例子是 sumEach = partial(map,sum) ,这是一行,大致相当于:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • 只使用一个 for 回路 :当然也可以使用for循环。虽然从函数式编程的角度来看,非局部变量并没有那么优雅,但有时在命令式编程语言(如python)中,非局部变量会使代码更加清晰,因为人们非常习惯这样阅读代码。因为循环通常也是最有效的,当你只是做任何不建立类似列表的复杂操作时,列表理解和映射被优化(例如,求和或生成树等),至少在内存方面是有效的(不一定在时间上是有效的,最坏的情况下,除了一些罕见的病态垃圾收集打嗝之外,还有一个不变的因素)。

“巨蛇座”

我不喜欢“pythonic”这个词,因为我觉得pythonic在我眼里并不总是优雅的。尽管如此, 地图 滤波器 以及类似的函数(比如 迭代工具 模块)在风格上可能被认为是不通俗的。

懒惰

就效率而言,就像大多数函数式编程结构一样, 地图可能很懒 ,实际上在python中是懒惰的。这意味着你可以这样做(在 Python 3 )并且您的计算机不会耗尽内存并丢失所有未保存的数据:

>>> 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)) .

捏造的简单例子

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}
)

蟒蛇3号的效率比较

地图 现在懒惰:

% 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中(以及python2或python3中的生成器表达式),在最后一刻之前,将避免计算它们的值。通常这将超过使用 地图 . 缺点是,与大多数函数式语言相比,这在python中是非常有限的:只有在从左到右“顺序”访问数据时,才能获得这一好处,因为python生成器表达式只能按顺序计算 x[0], x[1], x[2], ... .

不过,假设我们有一个预先生成的函数 f 我们愿意 地图 ,我们忽略了 地图 立即强制评估 list(...) . 我们得到了一些非常有趣的结果:

% 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>)

结果以a a a/bbb/ccc的形式显示,其中a是在使用python 3的circa-2010英特尔工作站上执行的。??,b和c是在一个使用python 3.2.1的circa-2013 amd工作站上执行的,硬件非常不同。结果表明,映射和列表理解在性能上具有可比性,且受其他随机因素的影响最大。奇怪的是,我们唯一能说的似乎是,当我们期望列表理解时 […] 执行比生成器表达式更好的表达式 (...) , 地图 生成器表达式的效率也更高(再次假设计算/使用了所有值)。

重要的是要认识到这些测试假设了一个非常简单的函数(identity函数);但是这很好,因为如果函数很复杂,那么与程序中的其他因素相比,性能开销可以忽略不计。(用其他一些简单的东西来测试,比如 f=lambda x:x+x )

如果您擅长阅读python程序集,则可以使用 dis 模块查看这是否是幕后发生的事情:

>>> 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 

看来还是用 […] 语法比 名单(…) . 可悲的是 地图 类对于反汇编来说有点不透明,但是我们可以通过速度测试来实现。

Alex Martelli
Reply   •   9 楼
Alex Martelli    10 年前

map 在某些情况下(当你不是为了这个目的而制作lambda,而是在map和listcomp中使用相同的函数时)可能会在显微镜下更快。在其他情况下,列表理解可能更快,大多数(并非所有)蟒蛇认为它们更直接、更清晰。

使用完全相同的功能时,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