python(22): apply、map及函数工具、list的再构造(14.2-14.5)


               
               
               
                首先来复习python中函数的定义。在python中,有两种方式可以用来定义一个函数。用def可以定义有名字的函数。用lambda可以定义匿名函数。由于在实际调用一个函数时,实参的个数可能是一个也可能是两个,还可能是多个。因此在形参前面加上*或**来处理这个情况。比如:
  • def test(*x):
    •     if len(x)>0:
    •        for c in x:
    •             print c
    •     else:
    •         print None上面定义的函数的功能是把参数一个一个打出来(通过print),这个函数可以使用没有实参的形式调用,比如:test();也可以使用一个实参的形式调用,比如:test(4);还可以以多个实参的形式调用,比如:test(1,2,3,4)。但不管是哪种方式,在调用的时候实际参数的个数都已经确定下来了,一个就是一个,两个就是两个。现在假设在一个程序中有一个list,比如叫userInput,这个list中的成员是由用户在使用程序时交互确定的,在运行中可能有一个成员,也可能有多个成员,这一切都要看用户怎么操作。现在要用test函数把userInput打出来。那么使用下面这个形式是不行的:
      • test(userInput)
      这只会把userInput作为一个整体打出来,比如说用户选择后userInput=[1,2],那么打出来的是[1,2],而我们想要的结果是:
      1
      2
      这种形式的。很自然的,可能会想到应该用一个for循环:
    • for c in userInput:
    •     test(c)这当然能实现上面要求的功能。不过,在python中还有更简单的办法,就是使用内置函数apply:
      • apply(test, userInput)
      表示把userInput作为test的参数,也就是说比如在程序运行时,userInput得到的值是[1,2,3],那么就相当于test(1,2,3)。如果userInput得到的实际值是[1,2],那么就相当于test(1,2)。
      总之,定义函数时在形式参数面前加上*或**是为了,在调用这个函数的时候可以灵活地提供实参的个数。而apply则是为了可以用不定个数的实际参数来调用函数。有点晕。(我也想不到一个更好的表达方式,或更好的例子。书中说,在后面会看到应用apply强大能力的例子)
      其实还可以更简单,连apply都不用,直接写成:
      • test(*userInput)
      这表示的意思就是apply(test,userInput)。可以把函数定义的中形参前面的*理解为“打包”操作,也就是把多余的实参都打包在一个tuple中;可以把调用时的*理解为“解包”的操作,比如说这里表示:调用test函数,实参是对userInput“解包”后所得的东西。当然对于**也可以以相同的方式理解。在test(**D)的形式中,D必须为一个dictionrary,相当于test(key1=value1,key2=value2...)。
      (其实我不知道为什么会在python中出现这种古怪的东西,看这本书的前面几章时,觉得python太棒了,真的是很简洁。不过越往后看,觉得python越来越背离了它“简单”的宗旨)
      map函数在前面介绍过了,基本的形式是:
      • map(function, seq1,seq2...)
      map实际上是python中一大类函数的一个代表,这类函数可以被称为函数工具。常用的函数中,同类的还有filter和reduce。先来看filter:
      • filter(func or None,seq)
      它返回一个序列类型的值。表示把seq中的每个成员依次代入function中,如果为真则它将成为返回序列中的一个成员。filter函数类似于:
    • res = list()
    • for x in seq:
    •     if func(x):
    •         res.append(x)
    • res如果filter的第一个实参为None,则表示把seq的中真值挑出来。
      再来看reduce函数:
      • reduce(function, sequence[, initial])
      表示把sequence按照function提供的规则进行计算,最后算出一个值来。这个function必须具有两个参数,比如:
    • L = [1,2,3,4,5]
    • reduce((lambda x,y: x*y),L) ===> 120这就相当于把乘法应用于L的各个成员中,也就是1*2*3*4*5。reduce的第三个参数表示一个初始值。即初始值*1*2*3*4*5。python中有一个叫operator的模块,其中提供了很多操作,比如加法叫add,乘法叫mul。上面的这一小段代码可以写成:
    • import operator
    • L = range(6)[1:]
    • reduce(operator.mul, L)
      从上面介绍可以看出,map、filter或者reduce实际都是对序列类型数据的成员进行操作。由于这种操作在python是非常普遍的,因此在python 2.0后提供了一种叫"list的再构造"(英文名叫list comprehansions,不知道怎么翻合适)的语法形式来进行类似的操作。假设L = [1,2,3,4],那么:map((lambda x: x**2), L)就可以写成:[x**2 for x in L]。这就是所谓的"list的再构造"。认真来看看这个:
      • [x**2 for x in L]   ===> [1,4,9,16]
      首先,这个表达式用[]括起来了,表示这个表达式返回的结果是一个list。其次,后面一部分,也就是for x in L,很明显是把L中的成员依次取出来。第三,前面一部分,也就是x**2,表示对x的操作是"平方"。然后以每次计算后的值作为目标list的成员。其实可以把这一部分看成是目标list的“通项公式”,通过对这个“通项公式”代入不同的值,可以得到不同的结果。而这个“不同的值”就是由后面的部分,即for x in L,确定的。
      • [(x**2,x**3) for x in L] ===> [(1, 1), (4, 8), (9, 27), (16, 64)]
      当然大名顶顶的"list再构造"还有别的功能,它可以加上条件从句(if语句),比如:
      • [x**2 for x in L if x%2==0] ===>[4,16]
      "list再构造"还可以嵌套for这一部分,比如:
      • [x*y for x in L for y in L] ===> [1, 2, 3, 4, 2, 4, 6, 8, 3, 6, 9, 12, 4, 8, 12, 16]
      相当于:
    • res = []
    • for x in L:
    •     for y in L:
    •         res.append(x*y)
    • res当然,嵌套for的"list再构造"也可以加上条件判断。比如:
      • [x*y for x in L if x%2==0 for y in L if y%2 != 0]  ===>[2, 6, 4, 12]
      这个式子已经相当复杂,因此在以程序可读性著称的python世界中,最好不要使用这种东西。不过,也可见用“list再构造”也可以实现for或者map和filter的功能,但是list再构造的效率最高,其次map、filter这些内置函数,再次才是for。