Python的高级函数

本篇文章将介绍函数相关的几个高级用法,如创建匿名函数、递归函数、装饰器,偏函数等。

一、匿名函数

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次调用计数器,记录值为:11次调用计数器,记录值为:22次调用计数器,记录值为:33次调用计数器,记录值为:44次调用计数器,记录值为:5

本篇文章就到这里结束了,希望能给小伙伴们一些帮助,喜欢的小伙伴们可以三连支持一下!!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

8X_I

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值