参考教程:廖雪峰官网https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000
一、返回函数
高阶函数除了可以接受函数作为参数之外,还可以把函数作为返回值。
通常我们也可以通过以下方式求和:
def calc_sum(*args): sum=0 for n in args: sum=sum+n return sum
但如果有一种情况 ,不需要立刻得出求和结果,而是在后续的代码中根据需要再计算,这种情况不返回求和的结果,而是返回求和的函数:
def lazy_sum(*args): def funcsum(): sum=0 for n in args: sum=sum+n return sum return funcsum x=lazy_sum(1,3,5) #返回值赋给x,x是一个代入(2,3,5)元组局部变量的函数funcsum() #通过输出可以看出x是一个函数 print(x) #需要调用x()时候才输出求和结果 print(x())
对于上例,调用lazy_sum()时候传入的参数(1,3,5)成为了新创建的funcsum()函数的内部变量,这样的程序结构也称为“闭包(Closure)”。
需要注意,即便传入同样的参数,返回的函数也是不同的:
x1=lazy_sum(1,3,5) x2=lazy_sum(1,3,5) print(x1==x2) #False
需要注意的是,返回函数并不立即执行,直到需要调用它的时候才执行,根据执行的时候各个参数和变量的值输出结果,看下面的例子:
def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fs #返回一个函数列表 f1, f2, f3 = count() fx=count() print(f1()) print(f2()) print(f3()) print(fx[0]()) print(fx[1]()) print(fx[2]())
以上输出全为9,是因为在生成(返回)f1,f2,f3和函数列表fx的时候,它们内部的变量是i,而在执行的时候i已经变成了3。所以在使用闭包时需要牢记:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要使用循环,那只有再创建一个函数,把循环变量绑定到其参数上,这样的绑定在循环过程中就实现了,可以保证循环后也不变:
def mycount(): def f(j): def g(): return j*j return g fs=[] for i in range(1,4): #此时i在循环中已经把值传递给g函数的参数j了 fs.append(f(i)) return fs myf=mycount() print(myf[0]()) print(myf[1]()) print(myf[2]())
练习:
#利用闭包返回一个计数器函数,每次调用它返回递增整数 def createCounter(): #注意这里使用的变量是一个列表,这是避免一个报错bug mycounter=[0] def counter(): mycounter[0]=mycounter[0]+1 return mycounter[0] return counter ‘‘‘ python3中也可以用nonlocal先声明mycounter 这样就可以避免报错 def createCounter(): mycounter=0 def counter(): nonlocal mycounter mycounter=mycounter+1 return mycounter return counter ‘‘‘ tester=createCounter() while input()!=‘3‘: print(tester()) # 测试: counterA = createCounter() print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5 counterB = createCounter() if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]: print(‘测试通过!‘) else: print(‘测试失败!‘)
二、匿名函数
在Python中有时候不需要显示定义函数,这样也可以避免给函数起名的烦恼。
#关键字lambda就表示匿名函数,冒号前面的x是函数的参数 #返回值是x*x f=lambda x:x*x f(5) #返回25
上面代码中的lambda表达式等同于下面代码:
def f(x): return x*x
一个lambda表达式在map()中的应用:
la=list(map(lambda x:x**2+1,[1,2,3,4,5])) print(la) #输出[2,5,10,17,26]
练习:
#请用匿名函数改造下面的代码: def is_odd(n): return n % 2 == 1 L = list(filter(is_odd, range(1, 20))) L1=list(filter(lambda x:x%2==1,range(1,20))) print(L) print(L1)
三、装饰器
某些情况下,我们需要增强若干个函数的功能,比如打印日志,但又不希望改动函数内部的代码,则有一种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)
例如,定义一个能打印日志的装饰器:
def log(func): def wrapper(*args,**kw): print(‘call %s‘ % func.__name__) return func(*args,**kw) return wrapper @log def add(a,b): return a+b print(add(3,6))
输出结果如下:
call add
9
注意到通过‘@‘符号在函数定义前增加了语句,即给该函数增加了一个装饰器功能。相当于执行了语句:now=log(add)。log(add)返回的是一个函数,该函数的功能不仅会运行函数本身,还运行了装饰器的代码。但这里now变量指向了新的函数,即wrapper。
如果要在装饰器中本身需要传入参数,则需要编写一个返回decorator的高阶函数:
def log(text): def decorator(func): def wrapper(*args,**kw): print(‘%s %s():‘ % (text,func.__name__)) return func(*args,**kw) return wrapper return decorator @log(‘haha‘)#注意这里参数的用法 def add(a,b): return a+b print(add(3,9)) print(add.__name__)
我们可以发现,最后输出的函数名变成了装饰器内部返回的函数名了,为避免一些依赖函数名的代码执行错误,可以通过Python内置的functools.wraps:
import functools def log(func): @functools.wraps(func) def wrapper(*args,**kw): print(‘call %s:‘ % func.__name__) return func(*args,**kw) return wrapper @log def add(a,b): return a+b print(add(3,9)) print(add.__name__)
练习:
#请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间: #-*- coding: utf-8 -*- import time, functools def metric(fn): @functools.wraps(fn) #保持原始函数名 def timedeco(*args,**kws): start_time=time.time() print(‘START TIME:‘+time.asctime(time.localtime(start_time))) x=fn(*args,**kws) end_time=time.time() print(‘END TIME:‘+time.asctime(time.localtime(end_time))) print(‘%s executed in %s ms‘ % (fn.__name__, str(end_time-start_time))) return x return timedeco # 测试 @metric def fast(x, y): time.sleep(1.0012) return x + y; @metric def slow(x, y, z): time.sleep(1.1234) return x * y * z; f = fast(11, 22) print(f) print(‘\n\n\n‘) s = slow(11, 22, 33) print(s) if f != 33: print(‘测试失败!‘) elif s != 7986: print(‘测试失败!‘)