本文摘自《流畅的Python》(《FluentPython》),作者Luciano Ramalho,译者安道 吴珂
=============
序言
就像“什么是美”没有确切的答案一样,“什么是Python风格”也没有标准答案。如果回答“地道的Python”,不能让人100%满意,因为对你来说是“地道的”,在我看来却可能不是。但我可以肯定的是,“地道”并不是指使用最鲜为人知的语言特性。
先决条件
- 归约函数(reduce、sum、any、all)将序列或有限的迭代对象变成一个聚合结果
- reduce函数在目前的更新中已经被收进functools包中了
- 函数签名
reduce(function, iterable, initializer)
,
其中第三个参数initializer
是可选的。它避免了空序列抛出异常,如果令了这个参数,将从这个参数开始。(一般为恒等值,如+|^等令为1,+ &令为0)。function
是一个接收两个参数的函数。 - 调用的一般过程为:如序列[1,2,3,4,5]
fun(1,2) (=a)
fun(a, 3) (=b)
fun(b, 4) etc...
问题提出
Python-list上有一篇题为“Pythonic Way to Sum n-th List Element?”(链接似已失效?)的话题与本篇讨论的reduce函数有关。
该话题发起人Guy Middleton说他不喜欢使用lambda表达式,问如下方案可否改进:
>>>my_list = [[1, 2, 3], [40, 50, 60], [9, 8, 7]]
>>>import functools
>>>functools.reduce(lambda a,, b: a+b, [sub[1] for sub in my_list])
60
这段代码包含了:lambda、reduce和列表推导。这对于讨厌lambda和看不上列表推导的人两边不讨好——这两种人都很多。如果使用lambda,或许就不应该使用列表推导——过滤除外,但这不是过滤((for x in list if x > 0)
其中if
就是过滤表达式)。
=============
本书的作者给出的方案是:
>>> functools.reduce(lambda a, b: a + b[1], my_list, 0)
60
但作者表示不会在真实代码如此写,(因为他也不喜欢lambda表达式)。此处仅仅是为了举例说明不使用列表推导怎么做。
=============
第一个答案
,来自Fernando Perez,IPython的创建者,强调了NumPy支持n维数组和n维切片:
>>> import numpy as np
>>>my_array = np.array(my_list)
>>>np.sum(my_array[:, 1]
60
(即一维全体,二维的下标1元素)
=============
第二个答案
,Guy Middleton推崇Paul Rubin和Skip Montanaro给出的下述方案:
>>> import operator
>>> functools.reduce(operator.add, my_list, 0)
60
其中,operator
库中包含诸如add
, xor
等常用数字运算函数,包含两个参数
=============
以及
,EvanSimpson问道:"这样做有什么错?"
>>> total = 0
>>> for sub in my_list:
... total += sub[1]
>>>total
60
foreach循环。
许多人都觉得这也很符合Python风格。Alex Martelli甚至说,Guido或许就会这么做。
作者喜欢这段代码,更喜欢David Eppstein对此给出的评论:
如果你想计算列表各个元素的和,写出的代码应该看起来像是在“计算元素之和”,而不是“迭代元素,维护一个变量t,再执行一系列求和操作”。如果不能站在一定高度上表明意图,让语言去关注低层次操作,那么要高级语言干嘛?
之后Alex Martelli又建议:
求和操作经常需要,我不介意Python提供一个这样的内置函数。但是,在我看来,“reduce(operator..add,...”不是好方法(作为一名APL老程序员和FP语言的爱好者,我应该喜欢,但我并不喜欢)。
随后
Alex建议提供并实现了sum()
函数并在之后的Python2.3中内置了。因此,Alex喜欢的句法变成了标准:(列表推导,仅能生成list)
>>> sum([sub[1] for sub in my_list])
60
下一年年末(2004年11月),Python2.4发布,这一版引入了生成器表达式。因此,作者建议,当前这个问题最符合Python风格的答案是:(生成任何类型的序列)
>>> sum(sub[1] for sub in my_list)
60
这样写不仅比reduce函数可读性更强,而且还避免了空序列导致的陷阱:sum([])
的结果是0,就这么简单
在这次讨论中,Alex Martelli指出,Python2内置的reduce函数成事不足败事有余,因为他推荐的地道编程方式难以理解。他的观点最优说服力:Python3把reduce函数移到functools模块中了。