首页 > temp > python入门教程 >
-
函数的特殊使用方式
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函数来进行装饰: 装饰器的使用
|
|
|
def mysum1(): |
|
from time import sleep |
|
total = 0 |
|
for _ in range(101): |
|
sleep(0.01) |
|
total += _ |
|
print(total) |
|
mysum1() |
在这里执行被装饰函数执行前的增强操作 5050 在这里执行被装饰函数执行前的增强操作
由上面可以看出,如果要装饰某个函数,只需要在定义这个函数时,在def语句的上一行添加@装饰器函数
即可。 对于装饰器装饰一个函数:
|
|
|
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 |
|
|
|
|
|
|
|
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
时,通过参数解包的方式传入参数。 装饰带有参数的函数:
|
|
|
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 |
|
|
|
|
|
|
|
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 inner |
1、在上面的代码中,return end – begin就是被装饰器的返回值。可以通过变量进行接收。
|
|
|
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 多个装饰器装饰同一个函数
对于某个函数,可以使用多个装饰器对其进行装饰,写法如下:
|
|
|
|
|
def 被装饰函数(): |
|
pass |
对于被多个装饰器装饰的函数,其装饰顺序为由最近到远,即decorator2会先装饰,然后是decorator1。
出处:https://www.cnblogs.com/sibide/p/17018461.html