私信  •  关注

ninjagecko

ninjagecko 最近创建的主题
ninjagecko 最近回复了
12 年前
回复了 ninjagecko 创建的主题 » 何时在python中使用map()[重复]

以下是一个可能的案例:

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

对比:

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

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

5 年前
回复了 ninjagecko 创建的主题 » 何时在python中使用map()[重复]

病例

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

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