病例
-
常见病例
:几乎总是,您需要在
蟒蛇
因为你对阅读你的代码的新手做的事情会更明显。(这不适用于其他语言,在这些语言中可能会用到其他习语。)您对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
看来还是用
[…]
语法比
名单(…)
. 可悲的是
地图
类对于反汇编来说有点不透明,但是我们可以通过速度测试来实现。