参考教程:廖雪峰官网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('测试失败!')
来源:https://www.cnblogs.com/tsembrace/p/8544104.html