1. 返回函数:
所谓的返回函数,指的是函数作为返回值。高阶函数除了可以接受函数作为参数外,同样可以接受函数作为结果返回。以下是一个可变参数的求和例子,一般求和函数是如此这般定义的:
1 >>> def sum(*args): 2 ... ax = 0 3 ... for x in args: 4 ... ax += x 5 ... return ax 6 ...
以上函数一旦定义,在调用这个函数的时候,只要传入参数就会立刻执行。但是,如果条件需要不要立刻求和,而是在后面的代码中,根据需要再计算该怎么办?可以不返回求和的结果,而是返回求和的函数!
1 >>> def lazy_sum(*args): 2 ... def sum(): 3 ... ax = 0 4 ... for n in args: 5 ... ax = ax + n 6 ... return ax 7 ... return sum 8 ... 9 >>> 10 >>> lazy_sum(12) 11 <function sum at 0x7f810add16e0> 12 >>> lazy_sum((1,2,3,4,5,6)) 13 <function sum at 0x7f810add17d0> 14 >>> 15 KeyboardInterrupt 16 >>># 以上是调用lazy_sum()函数的结果返回的不是求和的结果,而是求和函数
但是该如何才能够返回结果呢?演示如下:
1 >>> result = lazy_sum(12,13,14,15,16,17,18,19) 2 >>> result 3 <function sum at 0x7f810add17d0> 4 >>> #其实result现在是一个返回函数,要再调用该函数才可以返回结果 5 ... 6 >>> result() 7 124
在这个例子中,我们在函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
请再注意一点,当我们调用lazy_sum()
时,每次调用都会返回一个新的函数,即使传入相同的参数:
1 >>> result1 = lazy_sum(1,2,3,4,5,6,7,8,9,10) 2 >>> result2 = lazy_sum(1,2,3,4,5,6,7,8,9,10) 3 >>> result1 4 <function sum at 0x7f810add16e0> 5 >>> result2 6 <function sum at 0x7f810add1848> 7 >>> result1 == result2 8 False 9 >>>
如上代码显示:0x7f810add16e0是不等于0x7f810add1848的,因此比较结果为FALSE。但是,什么是“闭包”呢?记得在离散数学里面学习过闭包的知识,关系闭包.......。
2. 闭包:
注意到返回函数在其定义内部引用了局部变量args
,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了result()
才执行。我们来看一个例子:
1 >>> def get_count(): 2 ... fs = [] 3 ... for i in range(1,10): 4 ... def f (): 5 ... return i*i 6 ... fs.append(f) 7 ... return fs 8 ... f1,f2,f3 = get_count()
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。
你可能认为调用f1()
,f2()
和f3()
结果应该是1
,4
,9
,但实际结果是:
1 >>> f1() 2 9 3 >>> f2() 4 9 5 >>> f3() 6 9
全部都是9
!原因就在于返回的函数引用了变量i
,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i
已经变成了3,因此最终结果为9
。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
1 >>> def count(): 2 ... fs = [] 3 ... for i in range(1,4): 4 ... def f(j): 5 ... def g(): 6 ... return j*j 7 ... return g 8 ... fs.append(f(i)) 9 ... return fs 10 ... 11 >>> f1,f2,f3 = count() 12 >>> f1 13 <function g at 0x7f810add19b0> 14 >>> f1() 15 1 16 >>> f2() 17 4 18 >>> f3() 19 9 20 >>>
缺点是代码较长,可利用lambda函数缩短代码。
3. 匿名函数:
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。
在Python中,对匿名函数提供了有限支持。还是以map()
函数为例,计算f(x)=x2时,除了定义一个f(x)
的函数外,还可以直接传入匿名函数:
1 >>> map(lambda x : x * x,[1,2,3,4,5,6,7,8,9,]) 2 [1, 4, 9, 16, 25, 36, 49, 64, 81] 3 >>> map(lambda x : x * x,range(10)) 4 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 5 >>>
通过对比不难看出,匿名函数lambda x : x * x实际上就是一个求平方的函数:
1 >>> def f(x): 2 ... return x*x 3 ... 4 >>> f(4) 5 16 6 >>> f(40) 7 1600 8 >>>
关键字lambda
表示匿名函数,冒号前面的x
表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
1 >>> hanshu = lambda x : x*x*x 2 >>> hanshu 3 <function <lambda> at 0x7f810add1b90> 4 >>> hanshu(45) 5 91125 6 >>> hanshu(450) 7 91125000 8 >>>
同样,也可以把匿名函数作为返回值返回,比如:
1 >>> def build(x,y): 2 ... return lambda: x*x + y*y 3 ... 4 >>> build(5,6) 5 <function <lambda> at 0x7f810add1c80> 6 >>>
4. 偏函数
Python的functools
模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下:
int()
函数可以把字符串转换为整数,当仅传入字符串时,int()
函数默认按十进制转换:
1 >>> 2 >>> int(‘123‘) 3 123 4 >>> int(‘123454567‘) 5 123454567 6 >>>
但int()
函数还提供额外的base
参数,默认值为10
。如果传入base
参数,就可以做N进制的转换:
1 >>> int(‘123‘,base = 8) 2 83 3 >>> int(‘123‘,base = 10) 4 123 5 >>> int(‘123‘,base = 16) 6 291 7 >>> int(‘123123‘,base = 16) 8 1192227 9 >>>
假设要转换大量的二进制字符串,每次都传入int(x, base=2)
非常麻烦,于是,我们想到,可以定义一个int2()
的函数,默认把base=2
传进去:
1 >>> def int2(x ,base = 2): 2 ... return int(x,base) 3 ... 4 >>> int2(100) 5 Traceback (most recent call last): 6 File "<stdin>", line 1, in <module> 7 File "<stdin>", line 2, in int2 8 TypeError: int() can‘t convert non-string with explicit base 9 >>> int2(‘100‘) 10 4 11 >>>
注意:在传入参数的时候,要以字符串的形式传入,否则会报出“TypeError”的类型错误。要使用偏函数的时候还得自己定义,是不是感觉很麻烦?因此,python提供了functools.partial
就是帮助我们创建一个偏函数的,不需要我们自己定义int2()
,可以直接使用下面的代码创建一个新的函数int2
:
1 >>> import functools 2 >>> int2 = functools.partial(int,base = 2) 3 >>> int3 = functools.partial(int,base = 16) 4 >>> int2(‘100‘) 5 4 6 >>> int3(‘100‘) 7 256 8 >>>
所以,简单总结functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
注意到上面的新的int2
函数,仅仅是把base
参数重新设定默认值为2
,但也可以在函数调用时传入其他值:
1 >>> int3(‘100‘,base = 10) 2 100 3 >>> int2(‘100‘,base = 10) 4 100 5 >>>
最后,创建偏函数时,实际上可以接收函数对象、*args
和**kw
这3个参数,当传入:
1 int2 = functools.partial(int, base=2)
实际上固定了int()函数的关键字参数base
,也就是:
1 int2(‘10010‘)
相当于:
1 kw = { base: 2 } 2 int(‘10010‘, **kw)
当传入:
1 max2 = functools.partial(max, 10)
实际上会把10
作为*args
的一部分自动加到左边,也就是:
1 max2(5, 6, 7)
相当于:
1 args = (10, 5, 6, 7)
2 max(*args)
结果 = 10