本篇文章将介绍函数相关的几个高级用法,如创建匿名函数、递归函数、装饰器,偏函数等。
一、匿名函数
Python中,可以不用def关键字创建函数,使用lambda表达式创建匿名函数,语法格式如下
lambda param1,...paramN:expression
匿名函数也是函数,与普通函数一样,参数也是可选的,如下,使用lambda表达式创建一个函数对象
func1=lambda x,y:x+y
print("lambda对象类型:",type(func1))
result=func1(1,2)
print("匿名函数func1执行结果:",result)
def func2(x,y):
return x+y
result=func2(1,2)
print("函数func2执行结果:",result)
执行结果如下
lambda对象类型: <class 'function'>
匿名函数func1执行结果: 3
函数func2执行结果: 3
可以看出,lambda表达式不方便书写复杂的代码块
与标准的函数一样,匿名函数同样支持可选参数、可变参数等语法
二、递归函数
递归函数就是在一个函数内部调用自身的函数,本质上是一个循环,循环结束的点就是递归出口。如下,在第2行,当输入参数item等于1时,就退出当前函数。
def add(item):
if item == 1:
return 1
return item + add(item-1)
try:
result=add(999)
print(result)
except Exception as e:
print("错误信息:\n",e)
执行结果如下,出现异常,提示程序超过了最大递归深度。要想解决该问题,首先需要理解函数的调用方式。在计算机中,函数名、参数、值类型等,都是存放在栈上的。每进行一次函数调用,就会在栈上加一层,函数返回就减一层,由于栈的大小是有限的,递归次数过多就会导致堆栈溢出。
错误信息:
maximum recursion depth exceeded in comparison
在Python3中,默认栈的大小是998,调用sys.setrecursionlimit调整栈的大小可以解决上述问题。如下,其中第3行将栈的大小设置为2000,只要递归次数不超过2000,程序执行就不会报错。
import sys
sys.setrecursionlimit(2000)
def add(item):
if item == 1:
return 1
return item + add(item-1)
counter=999
result=add(counter)
print("递归调用次数:",counter)
print("递归调用结果:",result)
执行结果如下,输出递归调用次数和结果
递归调用次数: 999
递归调用结果: 499500
实际上,将counter的值改为2001,仍然会触发堆栈溢出的异常。
彻底解决堆栈溢出的方式是使用尾递归+生成器。尾递归是指在返回的时候,仅调用自身,不包含其他运算式,如加减乘除等,同时使用yield关键字返回生成器对象。如下,在第3行定义的add_recursive函数体内,使用yield返回时(此时该函数变成了生成器),返回的是这个函数本身,而不是一个具体的值。此时编译器内部就可以对尾递归进行优化,不论递归调用多少次,都返回一个函数栈,由此来避免堆栈溢出。
import types
def add_recursive(cur_item,cur_compute_result=1):
if cur_item == 1:
yield cur_compute_result
yield add_recursive(cur_item-1,cur_item+cur_compute_result)
def add_recursive_wapper(generator,item):
gen=generator(item)
while isinstance(gen,types.GeneratorType):
gen=gen.__next__()
return gen
print(add_recursive_wapper(add_recursive,10000))
执行结果如下
50005000
三、装饰器
如下,在loop方法中创建变量time1和time2,用来计算循环耗时。这种业务常见于性能测试。若是需要测试的方法特别多,那么将变量分别拷贝到不同的方法中,如第5行和第8行代码,这样会造成代码冗余,也不好维护。
import datetime
import time
def loop():
time1=datetime.datetime.now()
for i in range(count):
time.sleep(1)
time2=datetime.datetime.now()
print("循环耗时:",(time2-time1).seconds)
针对这类非业务的功能性需求,在设计模式中使用装饰器模式来改善代码。
装饰器模式的核心思想是在一个现有的方法上扩展功能,而不修改原有的代码。如果直接采用设计模式的写法来扩展函数功能,代码会稍显繁琐。基于这种思路,在被扩展的方法上加" @装饰器名称 "语法,即可完成扩展,如下。log方法是一个高阶函数,在该函数中继续创建decorate函数,用来封装被装饰的decorate方法。
import datetime
import time
def log(func):
def decorate(*args,**kw):
print("被装饰的函数名称:",func.__name__)
time1=datetime.datetime.now()
func(*args,**kw)
time2=datetime.datetime.now()
print("循环耗时:",(time2-time1).seconds)
return decorate
@log
def decorated():
print("decorated函数被装饰后,函数名为:",decorated.__name__)
for i in range(count):
time.sleep(1)
decorated()
执行结果如下,可以看到,log方法中的变量func指向了decorated方法,decorated方法被装饰后指向了decorate函数。当在调用decorated方法的时候,实际上是调用了decorate函数,因此可以顺利完成性能检测。
被装饰的函数名称: decorated
decorated函数被装饰后,函数名为: decorate
循环耗时:5
四、偏函数
偏函数是函数式编程思想和可选参数功能融合在一起的函数。多数情况下,需要利用原有代码的功能,但又不能修改原有代码,也不想编写新的代码,这时就需要用到偏函数。使用functools.partial方法创建偏函数,如下。本质上,偏函数只是使用partial方法将fun方法的参数固定住,将参数变为可选参数。
from functools import partial
def fun(a,b,c,d,e):
return a+b+c+d+e
partial_fun=partial(fun,b=2,c=3,d=4,e=5)
result=partial_fun(10)
print("调用偏函数:",result)
执行结果如下
调用偏函数: 24
五、内建函数
在Python中,有一个builtins.py的模块,称为内建模块,里面定义了众多函数,称为内建函数。本小节将着重介绍基本的、常用的内建函数。
1.map函数
map函数有两个参数,一个是待执行的方法,另一个是可迭代的对象。
map(func,*iterables)
map函数的作用是对对象(iterables)中的每一项都执行一边设定的方法(func)。注意,调用map函数时,返回的是map类的实例,此时func方法并没有被执行,这称为惰性函数。然后根据需要返回的类型,在map实例上调用list、tuple、set。dict等方法触发回调函数(func)的执行,得到最终结果,如下
def func(item):
return item+1
data1=[1,2,3,4,5,6,7,8,9,10]
data2=map(func,data1)
print("输出map返回值类型:",type(data2))
print("将map对象转为列表:",tuple(data2))
执行结果如下
输出map返回值类型: <class 'map'>
将map对象转为列表: (2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
2.reduce函数
reduce函数有三个参数,一个是待执行的方法,一个是序列,最后一个是初始值。
reduce(function,sequence,initial=None)
reduce函数的作用是,在没有初始值的情况下,首次执行,则从序列中取出两个元素,传入function参数得到一个结果,然后再从序列中按顺序取出下一个元素,和该结构再次传入function参数,直到把序列中的所有元素取完;若是设置了初始值,首次执行则从系列中华取出一个元素,并和初始值一起传function参数,同样将得到的结果和序列中的下一个元素继续传入function参数,直到序列取完,如下
from functools import reduce
def func(item1,item2):
return item1 + item2
data1=[1,2,3,4,5,6,7,8,9,10]
data2=reduce(func,data1)
print("序列求和:",data2)
data2=reduce(func,data1,10000)
print("序列求和:",data2)
执行结果如下
序列求和: 55
序列求和: 10055
3.filter函数
filter函数有两个参数,一个是待执行的方法,另一个是可迭代的对象。
filter(function or None,iterable)
filter函数的作用是从iterable参数中逐个取出元素,然后传入function参数进行计算,返回计算结果为True的元素。与map函数类似,该函数同样是" 惰性的 ",如下
def func(item):
if item % 2 == 0:
return True
data1=[1,2,3,4,5,6,7,8,9,10]
data2=filter(func,data1)
print("输出filter返回值类型:",type(data2))
print("将filter对象转为列表:",list(data2))
执行结果如下
输出filter返回值类型: <class 'filter'>
将filter对象转为列表: [2, 4, 6, 8, 10]
4.sorted函数
sorted函数可以对序列进行排序,如下,默认是升序,使用reverse参数调整排序方向,使用key参数,可以将序列中的元素按key参数指定的方法执行,如第5行,对每个元素调用abs方法(求绝对值),然后再进行排序。
data1=[6,4,10,3,9,2,8,5,7,1]
print("对列表排序,默认升序:",sorted(data1))
print("对列表降序排列:",sorted(data1,reverse=True))
data2=[-10,-1,0,30,28,15]
print("使用key参数对每个元素按绝对值排序:",sorted(data2,key=abs))
执行结果如下
对列表排序,默认升序: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
对列表降序排列: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
使用key参数对每个元素按绝对值排序: [0, -1, -10, 15, 28, 30]
六.闭包
Python是一门支持函数式编程的语言。在支持函数式编程的语言中,都会有闭包这个概念。简单来说,闭包就是在返回的内部函数中,引用了外部函数的局部变量。如下,其中内部函数fun2引用了外部函数fun1的局部变量local_val,于是local_val变量就与fun2函数形成闭包。在第11行,调用fun1函数返回的count就是fun2函数的引用。在for循环中,调用count时就会触发fun2函数的执行,闭包会记住上次执行的结果,因此在循环调用时,local_val变量会进行累加。
def fun1():
local_val=[0]
def fun2():
local_val[0]+=1
return local_val[0]
return fun2
count=fun1()
for i in range(5):
print("第%s次调用计数器,记录值为:%s"%(i,count()))
执行结果如下
第0次调用计数器,记录值为:1
第1次调用计数器,记录值为:2
第2次调用计数器,记录值为:3
第3次调用计数器,记录值为:4
第4次调用计数器,记录值为:5
本篇文章就到这里结束了,希望能给小伙伴们一些帮助,喜欢的小伙伴们可以三连支持一下!!