特殊的函数表达式有哪些类型(函数的特殊使用方式)
5.4 函数的特殊使用方式
5.4.1 匿名函数
所谓匿名函数 ,即不再使用def语句这样标准形式定义的函数 。Python中可以使用lambda关键字来创建匿名函数 。用lambda创建的匿名函数的函数体比def定义的函数体要简单 。语法如下:
lambda [参数1[,参数2],....参数n]]:表达式
lam_sum = lambda arg1, arg2: arg1 + arg2 print(lam_sum(10, 20))30
上述代码中 ,第一行定义了一个lambda函数 ,执行两个数的和运算 ,并且把该lambda函数命名为lam_sum 。然后通过lam_sum()函数实现求和的功能 。
Lambda创建的匿名函数中只能封装有限的逻辑进去 。
lambda函数拥有自己的命名空间 ,且不能访问自有参数列表之外或全局命名空间里的参数 。
实际上 ,一般在使用匿名函数时是不会再为创建的匿名函数命名的 。因为这样失去了匿名函数的简便性 。在有些场景是需要传入函数 ,需要的逻辑并不是很复杂 。但是又不想再创建一个 ,这个时候就可以直接使用匿名函数了 。如下: print(list(map(lambda x: x * x, [1, 2, 3, 4, 5])))[1, 4, 9, 16, 25]
5.4.2 递归调用
在Python定义函数时 ,函数体中可以调用其他函数 ,甚至可以调用自己 。这种自己调用自己的方式叫做递归调用。下面是一个递归式函数定义:
def recursion(): return recursion()显然 ,对于上面定义的函数 ,如果你运行它,你将发现运行一段时间后 ,这个程序崩溃了(引发异常) 。
从理论上说 ,这个程序将不断运行下去,但每次调用函数时 ,都将消耗一些内存 。因此函数调用次数达到一定的程度(且之前的函数调用未返回)后 ,将耗尽所有的内存空间 ,导致程序终止并显示错误消息“超过最大递归深度(maximum recursion depth exceeded ,默认最大为1000次) ”。
可以通过以下代码修改最大递归深度: import sys sys.setrecursionlimit(99999)这个函数中的递归称为无穷递归(就像以 while True 打头且不包含 break 和 return 语句的循环被称为无限循环一样) ,因为它从理论上说永远不会结束 。你想要的是能对你有所帮助的递归函数 ,这样的递归函数通常包含下面两部分 。
基线条件:满足这种条件时函数将直接返回一个值 。
递归条件:包含一个或多个调用 ,这些调用旨在解决问题的一部分 。
这里的关键是 ,通过将问题分解为较小的部分 ,可避免递归没完没了 ,因为问题终将被分解成基线条件可以解决的最小问题 。
那么如何让函数调用自身呢?这没有看起来那么难懂 。前面说过 ,每次调用函数时 ,都将为此创建一个新的命名空间 。这意味着函数调用自身时,是两个不同的函数[更准确地说 ,是不同版本(即命名空间不同)的同一个函数]在交流 。你可将此视为两个属于相同物种的动物在彼此交流 。
递归示例1:通过递归的方式求一个数的阶乘 def factorial(p_int=0): if p_int == 0: # 基线条件 return 1 else: # 递归条件 return p_int * factorial(p_int - 1) print(factorial(10))3628800
递归示例2:通过递归的方式求幂
def power(x, n): return 1 if n == 0 else x * power(x, n - 1) print(power(2, 10))1024
递归示例3:通过递归的方式解决汉诺塔问题
def move(n, a=A, b=B, c=C): if n == 1: print(移动, a, -->, c) else: move(n - 1, a, c, b) move(1, a, b, c) move(n - 1, b, a, c) move(4)移动 A --> B
移动 A --> C
移动 B --> C
移动 A --> B
移动 C --> A
移动 C --> B
移动 A --> B
移动 A --> C
移动 B --> C
移动 B --> A
移动 C --> A
移动 B --> C
移动 A --> B
移动 A --> C
移动 B --> C在某些特殊的问题中 ,如果通过普通的循环方式虽然也可以实现,但在使用了递归的方式后代码更加简单 。逻辑也更加清楚 。
理论上 ,所有的递归函数都可以写成循环的方式 ,但循环的逻辑不如递归清晰 。
使用递归函数需要注意防止栈溢出。在计算机中 ,函数调用是通过栈(stack)这种数据结构实现的 ,每当进入一个函数调用 ,栈就会加一层栈帧 ,每当函数返回 ,栈就会减一层栈帧 。由于栈的大小不是无限的 ,所以 ,递归调用的次数过多会导致栈溢出 。5.4.3 偏函数
参考:偏函数
介绍函数参数的时候 ,我们讲到 ,通过设定参数的默认值 ,可以降低函数调用的难度。而偏函数也可以做到这一点 。
int()函数可以把字符串转换为整数,当仅传入字符串时 ,int()函数默认按十进制转换:
>>> int(12345)12345
但int()函数还提供额外的base参数 ,默认值为10 。如果传入base参数,就可以做N进制的转换:
>>> int(12345, base=8)5349
>>> int(12345, 16)
74565
假设要转换大量的二进制字符串 ,每次都传入int(x, base=2)非常麻烦 ,于是 ,我们想到 ,可以定义一个int2()的函数 ,默认把base=2传进去:
def int2(x, base=2): return int(x, base)这样 ,我们转换二进制就非常方便了:
>>> int2(1000000)
64
>>> int2(1010101)
85
functools.partial就是帮助我们创建一个偏函数的 ,不需要我们自己定义int2() ,可以直接使用下面的代码创建一个新的函数int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2(1000000)64
>>> int2(1010101)
85
所以 ,简单总结functools.partial的作用就是 ,把一个函数的某些参数给固定住(也就是设置默认值) ,返回一个新的函数 ,调用这个新函数会更简单 。
注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2 ,但也可以在函数调用时传入其他值:
>>> int2(1000000, base=10)1000000
最后 ,创建偏函数时,实际上可以接收函数对象 、*args和**kw这3个参数 ,当传入:
>>> int2 = functools.partial(int, base=2)
实际上固定了int()函数的关键字参数base ,也就是:>>> int2(10010)
相当于:
>>> kw = { base: 2 }
>>> int(10010, **kw)
当传入:
>>> max2 = functools.partial(max, 10)
实际上会把10作为args的一部分自动加到左边 ,也就是:
>>> max2(5, 6, 7)
相当于:
>>> args = (10, 5, 6, 7)
>>> max(args)10
当函数的参数个数太多 ,需要简化时 ,使用functools.partial可以创建一个新的函数 ,这个新函数可以固定住原函数的部分参数 ,从而在调用时更简单 。
5.4.4 闭包
闭包就是一种函数的嵌套 ,首先定义了一个函数 ,称之为外部函数 。在这个外部函数体中又定义了一个内部函数 ,并且这个内部函数体中使用到了外部函数的变量 。外部函数最后return了内部函数 。那么这个外部函数以及内部函数就构成了一个特殊的对象 ,称之为闭包 。
闭包避免了使用全局变量 ,使得局部变量在函数外被访问成为可能,相比较面向对象 ,不用继承那么多的额外方法 ,闭包占用了更少的空间 。
闭包示例 a = 1 def out_fun(b): c = 3 def in_fun(d): print(a + b + c + d) return in_fun infun = out_fun(2) infun(4)10
可以看到,在内部函数in_fun中访问到了全局变量a 、外部函数out_fun的局部变量c以及参数b 。
5.4.4.2 装饰器装饰器(decorator)的本质:函数闭包(function closure)的语法糖(Syntactic sugar) 。通过给函数装饰 ,可以加强函数的功能或者增加原本函数没有的功能 。
装饰器在第一次调用被装饰函数时进行增强 ,并且只增强一次。
让我们先从一个简单的函数开始吧 。假设现在有一个函数 ,用来计算从1到100累加之和 ,并输出结果 。为了避免计算太快 ,我们在使用循环累加时设置等待0.01秒 ,函数定义如下: def mysum1(): from time import sleep total = 0 for _ in range(101): sleep(0.01) total += _ print(total) mysum1()5050
此时 ,如果我们想要知道调用这个函数执行一共花了多少时间 ,我们可以在执行前后获取时间再经过计算得到 ,也可以通过改造函数 ,在函数内部函数体前后获取时间计算得到。但是这两种方法都比较麻烦 ,尤其是第二种方法 ,需要侵入式修改函数代码 。
这个时候就可以通过为函数添加装饰器来实现了 。下面是创建装饰器的一般方法:
装饰器的简单定义 def decorator1(func): def inner(): print(在这里执行被装饰函数执行前的增强操作) func() # 执行被装饰的函数 print(在这里执行被装饰函数执行前的增强操作) return inner从上面可以看出,装饰器也是一个函数 。只不过装饰器接收的参数是被装饰的函数 。然后再装饰器内部定义一个函数 ,该内部函数体中执行要增强的操作代码以及执行被装饰的函数 。最后再return该内部函数 。
接下来是用装饰器来对某个函数进行装饰 。我们以上面定义的mysum1函数来进行装饰:
装饰器的使用 @decorator1 def mysum1(): from time import sleep total = 0 for _ in range(101): sleep(0.01) total += _ print(total) mysum1()在这里执行被装饰函数执行前的增强操作
5050
在这里执行被装饰函数执行前的增强操作由上面可以看出 ,如果要装饰某个函数,只需要在定义这个函数时 ,在def语句的上一行添加@装饰器函数即可 。
对于装饰器装饰一个函数: @decorator def myfun(): print("hello")上面的代码等价于:
def myfun(): print("hello") myfun = decorator(myfun)当一个装饰器装饰函数时 ,函数的功能增强了 ,因为在调用这个函数时 ,实际上调用的是在定义装饰器函数时 ,其内部函数 。而此时内部函数是由增强功能命令和原被装饰函数组成 。
创建一个统计函数运行时长的装饰器 import time def decorator1(func): def inner(): begin = time.time() func() # 执行被装饰的函数 end = time.time() print(f"函数`{func.__name__}`运行的总时间为:{end - begin:.3} 秒") return inner @decorator1 def mysum1(): from time import sleep total = 0 for _ in range(101): sleep(0.01) total += _ print(total) mysum1()5050
函数mysum1运行的总时间为:1.59 秒 5.4.4.2.2 被装饰函数接收参数在上面的例子中 ,通过装饰器函数decorator装饰的函数是不能有输入参数的 ,在实际使用中并不是很方便 。
通过对装饰器进行改造可以避免这种情况 ,从而使装饰器函数有更广泛的用途 。
装饰器定义:让被装饰函数接收参数 import time def decorator2(func): def inner(*args, **kwargs): begin = time.time() func(*args, **kwargs) # 执行被装饰的函数 end = time.time() print(f"函数`{func.__name__}`运行的总时间为:{end - begin:.3}") return inner需要改造的地方:
1 、为装饰器函数decorator的内部函数inner在定义时增加收集位置形参和收集关键字形参
2 、在装饰器函数decorator的内部函数inner函数体中 ,执行被装饰器装饰的函数func时 ,通过参数解包的方式传入参数。
装饰带有参数的函数: @decorator2 def mysum2(a, b): from time import sleep total = a for _ in range(total + 1, b + 1): sleep(0.01) total += _ print(total) mysum2(1, 100)5050
函数mysum1运行的总时间为:1.56 5.4.4.2.3 装饰器函数接收参数通过上面对装饰器进行改造 ,可以使的被装饰的函数可以输入参数 。上面的装饰器函数decorator2可以计算被装饰的函数执行时间 ,但是只能获取到执行一次的时间 。如果想要通过参数获取执行任一次的时间,则需要使得装饰器可以接收参数。
装饰器定义:装饰器接收参数 import time def decorator3(n): def inner(func): def wrapper(*args, **kwargs): begin = time.time() for _ in range(iteration): func(*args, **kwargs) end = time.time() print(f"函数`{func.__name__}`运行的总时间为:{end - begin:.3}") return wrapper return inner需要改造的地方:
1 、装饰器函数此时并不是通过参数来传入被装饰的函数 ,而是定义装饰器自己的参数 ,演示时使用的是一个位置参数n,在后续如果遇到比较复杂的情况下也可以使用关键字形参 、*args 、**kwargs等收集参数 。
2 、内部函数inner用来收集被装饰的函数 。
3 、内部函数inner的内部函数wrapper用来收集被装饰的函数的参数 。并编写需要增强的命令 。最终要执行被装饰的函数其实就是执行这个wrapper函数 。
装饰器接收参数: import time def decorator3(n): def inner(func): def wrapper(*args, **kwargs): begin = time.time() for _ in range(n): func(*args, **kwargs) end = time.time() print(f"函数`{func.__name__}`运行的总时间为:{end - begin:.3}") return wrapper return inner @decorator3(10) def mysum3(a, b): from time import sleep total = a for _ in range(total + 1, b + 1): sleep(0.01) total += _ print(total) mysum3(1, 10) # 等价于:mysum3 = decorator3(10)(mysum3)55
55
55
55
55
55
55
55
55
55
函数mysum3运行的总时间为:1.41 5.4.4.2.4 装饰器的返回值如果你了解了上一节的内容 ,很容易想到只要在wrapper函数中return就是被装饰函数的返回值 。
装饰器定义:接收被装饰函数的返回值 import time def decorator3(n): def inner(func): def wrapper(*args, **kwargs): begin = time.time() for _ in range(n): func(*args, **kwargs) end = time.time() print(f"函数`{func.__name__}`运行的总时间为:{end - begin:.3}") return end - begin return wrapper return inner1 、在上面的代码中 ,return end – begin就是被装饰器的返回值 。可以通过变量进行接收 。
@decorator3(3) def mysum3(a, b): from time import sleep total = a for _ in range(total + 1, b + 1): sleep(0.01) total += _ print(total) total_time = mysum3(1, 10) print(total_time)函数mysum3运行的总时间为:1.42
1.4218323230743408 5.4.4.2.5 多个装饰器装饰同一个函数对于某个函数 ,可以使用多个装饰器对其进行装饰 ,写法如下:
@decorator1 @decorator2 def 被装饰函数(): pass对于被多个装饰器装饰的函数 ,其装饰顺序为由最近到远 ,即decorator2会先装饰 ,然后是decorator1 。
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!