Python3从入门到实战
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.5 函数对象和lambda表达式

3.5.1 函数对象

1.函数也是对象

在Python中,函数也是对象,即function类型的对象,因此,可以和其他对象,如int、list对象一样使用。

● 用一个变量引用一个函数。

● 将函数作为另外一个函数的参数。

● 从一个函数里返回另外一个函数。

● 将函数存储在各种数据结构,如list、tuple、set里。

函数作为一个对象,也具有对象的三个属性,id、type和值。例如,对于下面的函数square(),可以检查它的id和type属性:

def square(x):
    return x*x

print(id(square))
print(type(square))

输出:

1767284622744
<class 'function'>

既然函数也是一个对象,当然可以将函数赋值给一个变量。例如:

fun=square

通过变量fun调用它引用的对象square:

fun(3.5)

输出:

12.25

函数名square和变量名fun都是同一个函数对象的名字:

print(id(fun))
print(id(square))
print(fun is square)

输出:

1767284521296
1767284521296
True

还可以把该函数赋值给更多的变量,唯一变化的是该函数对象的引用次数不断地增加,本质上,这些变量最终指向的都是同一个函数对象。

既然函数是对象,那么它就和其他对象一样可以放在一个如list这样的容器或集合里,也可以作为函数参数或返回值。

2.函数可以放在容器内

例如,下面的代码将两个函数square()和cube()放在funcs指向的字典对象中,通过for…in可以循环遍历这个funcs中的每个字典元素,并调用字典元素的值指向的函数:

def square(x):
    return x*x

def cube(x):
    """Cube of x."""
    return x*x*x

funcs={
    'sq': square,
    'cb': cube,
}

x=2
print(square(x))
print(cube(x))

for func in funcs:
    print(func, funcs[func](x))

输出:

4
8

sq 4
cb 8

当然也可以将函数放在其他容器,如一个list对象中:

fun_list=[square, cube]
for fun in fun_list:
    print(fun(x))

输出:

4
8

3.函数可以作为返回值

函数可以作为返回值。例如,下面的代码在函数Squ()中将另外一个函数square()作为返回值:

def Squ():
    return  square

f=Squ()
print(f(6))

输出:

36

4.函数可以嵌套

一个函数的内部可以定义另外一个函数。例如,下面的函数Square(),函数内定义了另外一个函数f():

def Square(x):
  def f():
      return x*x
  y=f()   #调用函数f()
  return y+x

print(Square(5))

调用Square(5)时会执行其中的“y=f()”和“return y+x”,然后print(Square(5))语句打印Square(5)的返回结果:

30

注意

嵌套函数可以访问其包围环境中的数据。例如,函数f()可以访问包围它的函数Square()的参数(局部变量)x。

嵌套函数不能修改包围环境中的变量,除非该变量在嵌套函数中被声明为nonlocal。例如:

def Square(x):
  def f():
      x=2
      return x*x
  y=f()
  return y+x

print(Square(5))

输出:

9

函数Square()中的x初始化值是指向5的变量,在函数f()中的“x=2”实际上定义了一个函数f()的局部变量,即指向这个对象2,然后函数f()返回这个x对象的乘积。也就是说,函数f()返回的值是4。而y+x中的x仍然是函数Square()中的参数,其值仍然是调用函数Square(5)时传递的值5,而不是函数f()中的局部变量x=2。因此,最后的结果是9。

但如果在函数f()中声明了“nonlocal x”,则这个x将是函数f()的包围环境,即函数Square(x)中的x。

当“x=2”时,即让x指向一个新的对象2,因为这个x就是函数Square()中的x,当退出函数f()后,函数Square()中的x则指向这个对象2,因此,最后的输出是6。nonlocal类似global,函数中用global声明的变量是外部变量,nonlocal声明的变量是其包围环境中的变量。例如:

def Square(x):
  def f():
      nonlocal x
      x=2
      return x*x
  y=f()
  return y+x

print(Square(5))

输出:

6

5.函数可以作为其他函数的参数

函数可以作为其他函数的参数。例如,下面的代码中“SquList([2,3,4,5], square)”将一个函数square()作为参数传递给函数SquList()的形式参数fun,而函数内部调用这个函数fun()对L的每个元素e进行计算:

def SquList(L, fun):
    for e in L:
      print(fun(e), end=" ")

SquList([2,3,4,5], square)

输出:

4 9 16 25

3.5.2 lambda表达式

1.lambda表达式(匿名函数)

Python中的函数一般都用def定义并有一个函数名,而lambda表达式(也称lambda函数匿名函数),是一个不用关键字def定义的没有函数名的函数,它主要用于定义简单的单行函数,即代码可以写在一行里,并且和普通函数一样,可以有参数列表。其定义格式为:

lambda 参数:语句

例如,下面定义一个带有参数x、y的lambda函数:

lambda x, y: x + y

输出:

<function __main__.<lambda>>

但这个lambda函数没有名字,所以无法使用它。既然函数是对象,当然也可以给lambda函数命名一个名字。例如,通过赋值运算符给lambda函数命名一个名字add:

add=lambda x, y: x + y

可以调用add引用的这个lambda函数,并传递实际参数给这个lambda函数。

print(add(3, 5))

输出:

8

尽管只有一行代码,但lambda表达式是一个函数而不是普通的表达式,它可以接收传入的参数,而普通表达式则无法接收参数。

2.lambda函数功能

lambda函数主要用作函数的参数。有些函数需要接收另外一个函数作为其参数,使用lambda函数可以避免专门为这些函数写一个作为其参数的普通函数。

例如,对一个可迭代对象排序的内置函数sorted(),其语法格式是:

sorted(iterable, key=None, reverse=False)

除要排序的可迭代对象形参iterable外,还有两个默认形参,其中的形参key必须是一个函数,该函数用于计算迭代访问的每个元素的key(关键字)值。可选参数reverse表示按key大小逆序排序,默认不是逆序,即reverse=False。例如:

alist=[-5,3,1, -7,9]
print(sorted(alist))
print(sorted(alist, reverse=True))

输出:

[-7, -5, 1, 3, 9]
[9, 3, 1, -5, -7]

上面的正序和逆序都是按照数据元素的值的大小进行比较而排序的。如果希望按照另外的某种大小比较方式(如绝对值)排序,就需要传递一个作为key参数的函数,这个函数用于计算一个数据元素的关键字值,函数sorted()将按照这个关键字值进行排序。为此,需要写一个函数Key()从数据元素e中得到其键值:

def Key(e):
  return abs(e)

可以将这个函数Key()传给函数sorted()的key形参:

print(sorted(alist, key=Key))

输出按照绝对值大小比较的有序列表:

[1, 3, -5, -7, 9]

上面的函数Key()只有一行代码,完全可以用lambda函数代替这个函数Key()作为函数sorted()的key参数:

print(sorted(alist, key=lambda x: abs(x)))
[1, 3, -5, -7, 9]

这种用lambda函数代替普通函数作为其他函数形参的方式,避免了单独编写一个普通函数,使得代码更加清晰可读。

list对象也有一个类似的排序方法sort:

list.sort([key=…, reverse=…])

可以同样用lambda函数作为其key参数:

alist=[(2, 2),(3, 4),(4, 1),(1, 3)]
alist.sort(key=lambda e:e[1])
print(alist)

输出:

[(4, 1),(2, 2),(1, 3),(3, 4)]

这里的lambda函数“lambda e:e[1]”将一个数据元素e作为参数,e是一个两个元素的tuple, e[1]返回这个tuple对象的第二个元素的值,作为整个lambda函数的返回值。

下面再以一些常用的,可以接收函数参数的函数作为例子,进一步演示lambda表达式的定义与使用。

3.内置函数map()和内置函数filter()

(1)内置函数map()。

内置函数map()的规范是:

map(function, *iterable)

其中,iterable是可变形参,即可以接收多个可迭代对象。内置函数map()将第一个参数function指向的函数对象作用于每个可迭代对象的每个数据元素上。内置函数map()返回一个迭代器对象(如list、tuple对象都是迭代器对象),也可以用返回的迭代器对象构造一个list或tuple对象。例如:

def square(x):
    return x*x

ret=map(square, [3,4,5,6,7])    #将square作用于list对象的每个元素上
print(tuple(ret))                 #用返回的迭代器对象ret构造一个tuple对象
ret=map(square, [3,4,5,6,7])
print(list(ret))                  #用返回的迭代器对象ret构造一个list对象

输出:

(9, 16, 25, 36, 49)
[9, 16, 25, 36, 49]

用返回的迭代器对象构造一个list对象,即语句“list(map(function, iterable))”实际上类似执行下面的代码:

[function(x)for x in iterable]

下面的代码可以验证这一点:

#用返回的迭代器对象构造一个list对象
alist=list(map(square, [3,4,5,6,7]))
#类似执行下面的代码
blist=[square(x)for x in [3,4,5,6,7]]
print(alist)
print(blist)

输出:

[9, 16, 25, 36, 49]
[9, 16, 25, 36, 49]

实际上,内置函数map()的第二个可变形参iterable可以接收多个可迭代对象,内置函数map()用第一个函数参数function同时作用于这些可迭代对象的对应元素上,如果这些可迭代对象的数据元素个数不一样,则内置函数map()只执行最小数目(数目最少的迭代器的数据元素个数)的操作。例如:

ret=map(lambda x , y : x * y, [1,4,3], [3,5,2,6])
print(tuple(ret))

以上代码传递了两个可迭代对象[1,4,3]和[3,5,2,6],但前者只有三个元素而后者有四个元素,因此,只对三对对应元素((1,3)、(4,5)、(3,2))执行lambda表达式。

输出:

(3, 20, 6)

上述的lambda表达式返回的对象是一个数,也可以让lambda表达式返回一个其他形式的对象。例如,让lambda表达式返回一个tuple:

ret=map(lambda x , y :(x * y, x+y), [1,4,3], [3,5,2,6])
print(tuple(ret))

输出:

((3, 4),(20, 9),(6, 5))

内置函数map()接收了两个list对象给可变形参iterable。因为两个list对象的数据元素个数分别是三个元素和四个元素,所以,内置函数map()用传递参数function的lambda表达式对它们的前三对数据元素执行计算。

再如:

ret=map(lambda x , y, z :(x * y*z, x+y+z), [1,4,3], [3,5,2,6], [7,8,9,10])
print(tuple(ret))

输出:

((21, 11),(160, 17),(54, 14))

(2)内置函数filter()。

内置函数filter()的规范是:

filter(function or None, iterable)

它接收一个函数(或空值对象)和一个可迭代对象,返回的是一个新的迭代器对象,新的迭代器对象的每个元素都是被内置函数function()判断为True的原迭代器中的元素。同样,可以用返回的迭代器对象构造一个list或tuple对象。例如:

numbers=range(-5, 5)
ret=filter(lambda x: x < 0, numbers)
less_than_zero=tuple(ret)
print(less_than_zero)

输出:

(-5, -4, -3, -2, -1)

上述代码中的内置函数filter()接收一个list对象,并用一个lambda函数判断可迭代对象numbers的每个数是否为负数,内置函数filter()返回的是这些负数构成的迭代器对象ret,用这个ret传递给函数tuple()以构造一个tuple对象,最后输出这个tuple对象。

总结

● 函数是function类型的对象,和其他对象一样,函数对象既可以作为函数参数、返回值,也可以存储在数据结构里。

● lambda函数是一个匿名函数,主要用于单行代码的函数,经常用作其他函数的参数。

● 以函数作为参数的一些有用的内置函数如map()、filter()。