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()。