社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

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

Derek Allums • 5 年前 • 1953 次点击  

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

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/42936
 
1953 次点击  
文章 [ 9 ]  |  最新文章 5 年前
lmiguelvargasf
Reply   •   1 楼
lmiguelvargasf    7 年前

我认为最变态的方法是使用列表理解而不是 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    8 年前

所以自从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    11 年前

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

ninjagecko
Reply   •   4 楼
ninjagecko    14 年前

以下是一个可能的案例:

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

对比:

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

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

Dan
Reply   •   5 楼
Dan    16 年前

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

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

raek
Reply   •   6 楼
raek    11 年前

事实上, 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    11 年前

你应该用 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    7 年前

病例

  • 常见病例 :几乎总是,您需要在 蟒蛇 因为你对阅读你的代码的新手做的事情会更明显。(这不适用于其他语言,在这些语言中可能会用到其他习语。)您对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    16 年前

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