Python函数化编程-译

Python: Lambda 函数

Python使用lambda支持在运行时创建匿名函数(没有绑定名称的函数)。这和函数编程中lambda是有区别的,但是这是个集成到Python中很有用的概念,它经常和一些典型的函数一起使用,例如:filter(), map()reduce()

下面一段代码显示了普通函数(f)定义和lambda函数(g)的区别:

>>> def f(x): return x**2
...
>>>print f(8)
64
>>>
>>> g = lambda x: x**2
>>>
>>> print g(8)
64

可以看到f()和g()运行结果是一样的,可以用相同的方式来使用。注意到lambda函数并没有使用return语句---经常包含一个被返回的表达式。并且你可以把lambda函数定义放置在任何需要函数的地方, 你也不需要将其赋值给一个变量。

下面的代码片段说明了lambda函数的使用。注意,为了支持嵌套函数,你必须安装Python2.2或以上的版本(在旧版本中,你必须通过一个默认参数来传递n来实现此例)。
>>> def make_incrementor (n): return lambda x: x+n
>>>
>>> f = make_incrementor(2)
>>> g = make _incrementor(6)
>>>
>>> print f(42), g(42)
 44  48
>>> print make_incrementor(22)(33)
 55
上面代码创建了make_incrementor函数,并在内部创建并返回了匿名函数。被返回的函数递增了他的参数值(求和)。

你可以创建很多不同的增量函数并把它们赋值给不同的变量,然后单独使用它们。正如最后一个例子所显示的, 你甚至可以不用为这个函数赋值--立刻调用,当他们不需要的时候忘掉他们。


下面讲一些更进一步的内容

>>> foo = [2, 18, 9, 22, 17, 24, 8, 12, 27]
>>> 
>>> print filter(lambda x: x % 3 == 0, foo)
[18, 9, 24, 12, 27]
>>> 
>>> print map(lambda x: x * 2 + 10, foo)
[14, 46, 28, 54, 44, 58, 26, 34, 64]
>>> 
>>> print reduce(lambda x, y: x + y, foo)
139

首先我们定义一组数字列表,然后我们使用标准函数filter(),map() 和reduce()对列表进行操作。所有的上述函数需要2个参数:一个函数和一个列表。

当然,我们可以在别处单独定义一个函数,然后将这个函数的名字作为filter()的参数。事实上,如果我们多次使用这个函数,或者这个函数写在单行比较复杂的时候,这绝对是个好主意。然而,如果我们只需要这个函数运行一次而且这个函数非常简单的时候(例如,只包含一个表达式,和上面的例子一样),使用lambda形式匿名函数(临时)并将其传递给filter()就会显得非常方便。这将创建非常紧凑和可读性强的代码。

在第一个例子中,filter()为列表中的每一个元素调用lambda函数,并返回一个新列表, 这个列表仅仅包含原列表中元素在调用lambda函数返回值为True的元素。在一个情况下,我们得到一个列表,列表中的元素全部都是3的倍数。表达式x%3==0计算x除以3的余数,并和0进行比较(x能被3正处,显示True)。

第二个例子中,map()用于转换我们的列表。列表中的每一个元素调用给定函数,然后新列表被创建,新列表中的元素由lambda函数创建。在这个例子中,原始列表中的每个元素被乘以2并加10.

最后,reduce()有些特殊。这个worker function需要2个而不是一个参数(我们称之为x,y),这个函数首先被列表的前2个 元素调用,然后以前2个元素调用函数的结果以及第三个元素作为参数调用函数,以此类推,知道所有的元素被处理。这意味着如果我们的列表包含n个元素, 函数将会被调用n-1次。最后调用返回的数据就是reduce()的结果。在上例中,他只用来简单的对参数求和, 所有我们得到所有列表元素的和。(在Python2.3中有一个内建函数sum()可以完成同样的工作并且更有效率)。

 

下面的例子是Python中计算质数的一个方法(并不是最有效的一种):

>>> nums = range(2, 50) 
>>> for i in range(2, 8): 
...     nums = filter(lambda x: x == i or x % i, nums)
... 
>>> print nums
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]

它是如何工作?首先,首先我们创建一个2到49的数字列表,并赋值给nums。然后我们使用for 循环枚举所有可能的除数,比如i取值2~7。所有包含这些因数的数字都不可能是质数,所有我们使用filter()函数除掉这些元素。(这个算法被称为the sieve of Eratosthenes)

在上面的例子中,filter()函数简单的讲:“留下列表中等于i或者被i除有非0余数的元素。其他元素去掉。”过滤循环完成后,只有质数留下。(除了函数化编程语言外)我还不知其他的语言如Python般使用内建特性完成如此紧凑、如此可读的代码。

注意:range()函数只返回从x~y-1个数字。例如:range(5,10)返回列表[5,6,7,8,9]。



下例中,一个字符串被分割成字符串列表,然后创建一个列表包含每个字符串的长度。

>>>sentence = 'It is raining cats and dogs'
>>>words = sentence.split()
>>>print words
['It', 'is', 'raining', 'cats', 'and', 'dogs']
>>>
>>>lengths = map(lambda word: len(word), words)
>>>print lengths
[2, 2, 7, 4, 3, 4]

我认为不需要解释,代码自身已经非常清晰的说明了自己。

当然, 代码也可以写成单个语句。固然,这在一定程度上影响可读性。(不太严重)

>>>print map(lambda w: len(w), 'It is raining cats and dogs'.split())
[2, 2, 7, 4, 3, 4]

 


这里有个来自UNIX脚本的例子:我们想找出文件系统中的所有的挂载点。我们执行外部'mount'命令,解析输出。

>>>import commands
>>>
>>>mount = commands.getoutput('mount -v')
>>>lines = mount.splitlines()
>>>points = map(lambda line: line.split()[2], lines)
>>>
>>>print points
['/', '/var', '/usr', '/usr/local', '/tmp', '/proc']

commands模块中的getoutput()函数运行给定的命令, 返回一个字符串输出。所以,首先我们把它分割成不同的行。然后我们使用带lambda的map()函数分隔每行(默认按照空白来分割)并返回第三个元素,即挂在点。

我们仍可以把上述代码写成单行的语句, 这样更紧凑但是牺牲了可读性:

print map(lambda x: x.split()[2], commands.getoutput('mount -v').splitlines())

['/', '/var', '/usr', '/usr/local', '/tmp', '/proc']

当写"real-world"脚本的时候, 最好把复杂的语句分开,这样容易理解他们的意思。而且,容易修改。

然后, 把一个命令分割成包含很多行的列表是很普遍的事情。在你解析外部命令的输出的时候,你会经常用到它。所以,练习在输出行中使用split是很普遍的, 然后在单独做其他的事情。这在紧凑性和可读性之间是个很好的平衡:

>>>lines =  commands.getouput('mount -v').splitlines()
>>>
>>>points = map(lambda line: line.split()[2], lines)
>>>print points
['/', '/var', '/usr', '/usr/local', '/tmp', '/proc']

另一个更好的方法可能是写一个小函数来完成这个任务, 这个小函数来封装运行命令和分隔输出的工作。

另一方面,你还可以使用列表表达式来建立新列表。有时这更具效率和可读性。前面的例子可以重新用列表表达式来写:

>>>lines=commands.getoutput('mount -v').splitlines()
>>>
>>>points=[line.split()[2] for line in lines]
>>>print points
['/', '/var', '/usr', '/usr/local', '/tmp', '/proc']
在很多情况下你可以使用列表表达式来代替map和filter。按照具体情况来决定。

注意:推荐在新版本的Python中使用命令模块。相反, 从Python2.4开始推荐使用subprocess模块。subprocess.check_output()可以取代commands.getoutput()。详情请参考文档。

posted @ 2012-08-21 18:54  Cymbidium  阅读(553)  评论(0编辑  收藏  举报